iOS 多线程总结

更多实用详见 Demo
本文导读

一些相关总结(同步、异步、并行、串行概念,GCD、NSOperation对比)
一、进程与线程概念
二、多线程-GCD
三、多线程-NSOperation
四、多线程-NSThread
五、队列概念
六、多线程面试题

同步、异步、并行、串行
  1. 同步和异步决定了要不要开启行的新的线程。同步在当前线程中执行任务,不具备开启新线程的能力。异步在新的线程中执行任务,具备开启新线程的能力。
  2. 并行和串行决定了任务的执行方式。并发多个任务并发(同时)执行。串行,一个任务执行完毕后,再执行下一个任务。
GCD、NSOperation对比:
  • GCD --> iOS 4.0
    (1)将任务(block)添加到队列(串行/并发(全局)),指定执行任务的方法(同步(阻塞)/异步)
    (2)拿到 dispatch_get_main_queue()。 线程间通信
    (3)NSOperation无法做到一次性执行、延迟执行、调度组(op相对复杂)
    (4)提供了更多的控制能力以及操作队列中所不能使用的底层函数

  • NSOperation ----> iOS 2.0 (后来苹果改造了NSOperation的底层)
    (1)将操作(异步执行)添加到队列(并发/全局)
    (2)[NSOperationQueue mainQueue] 主队列。 任务添加到主队列, 就会在主线程执行
    (3)提供了一些GCD不好实现的,”最大并发数“、依赖关系、暂停/继续 --- 挂起、取消所有的任务

  • 对比:
    (1)GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装
    (2)GCD只支持FIFO的队列,NSOperationQueue可以很方便的调整执行顺序、设置最大并发数量 (FIFO 就是先进先出)
    (3)NSOperationQueue可以很轻松的在Operation间设置依赖关系,而GCD需要写很多代码才能实现
    (4)NSOperationQueue支持KVO(键值观察者),可以监听operation是否正在执行 (isExecuted)、是否结束(isFinished)、是否取消(isCanceld)
    (5)GCD的执行速度比NSOperationQueue快(封装GCD,更高层的东西,性能不好,因为还要转化成GCD)快

  • 在项目什么时候选择使用GCD,什么时候选择NSOperation?
    · NSOperation:
    任务之间有依赖\或者要监听任务的执行情况
    项目中使用的优点是它是对线程的高度抽象,会使程序结构更好,子类化NSOperation的设计思路具有面向对象的优点(复用、封装),使得实现多线程支持,而接口简单,建议在复杂项目中使用。
    · GCD:
    任务之间不太依赖 项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

一、进程与线程概念:

  • 进程百度百科

    进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

  • 线程百度百科

    线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。 线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程

  • 进程总结

    1. 进程是指在系统中正在运行的一个应用程序
    2. 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
  • 线程总结

    1. 1个进程要想执行任务,必须得有线程(每1个进程至少要有一个线程
    2. 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行

    进程(process)是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。简而言之,一个程序至少有一个进程,一个进程至少有一个线程。一个程序就是一个进程,而一个程序中的多个任务则被称为线程。 线程只能归属于一个进程并且它只能访问该进程所拥有的资源。当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。应用程序(application)是由一个或多个相互协作的进程组成的。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

  • 线程与进程对比?

    1. 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
    2. 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
    3. 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源
    4. 系统开销: 进程切换(创建或撤消)时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销,效率要差一些。对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程
    5. 进程都有自己的独立地址空间。一个进程崩溃后,在保护模式下不会对其它进程产生影响。线程有自己的堆、局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮
    6. 线程是指进程内的一个执行单元,也是进程内的可调度实体。它可以与同进程的其他线程共享数据,但拥有自己的栈空间

    简而言之,一个程序至少有一个进程,一个进程至少有一个线程(即主线程)。一个程序就是一个进程,而一个程序中的多个任务则被称为线程。

  • 学习多线程,先了解手机里有几个重要的芯片:
    主芯片:CPU(双核)+GPU(相当于电脑里的显卡)
    内存(RAM):相当于电脑的内存条
    闪存芯片:相于于电脑的硬盘
    电源管理芯片
    蓝牙、wifi、gps芯片

  • 多线程应用:
    耗时操作(数据库中的读取,图片的处理(滤镜) )
    进程是来帮你分配内存的
    多线程开线程一般五条以内

二、多线程-GCD

GCD简介

全称Grand Central Dispatch,纯C语言,提供了非常多强大的函数

  • 优势:

    1. 苹果公司为多核的并行运算提出的解决方案
    2. GCD会自动利用更多的CPU内核(比如双核、四核)
    3. GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    4. 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
  • GCD中2个核心概念:

    1. 任务:执行什么操作
    2. 队列:用来存放任务
  • GCD的使用就两个步骤:

    1. 定制任务:确定想要做的事
    2. 将任务添加到队列中:GCD会自动将队列中的任务取出,放到对应的线程中执行。任务的取出遵循队列的FIFO原则(先进先出,后进后出)
  • 队列的类型: GCD的队列可以分为两种类型:
    · 并发队列
    (1) 队列中的任务 通常 会并发执行
    (2) 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    (3) 并发功能只有在异步(dispatch_async)函数下才有效
    · 串行队列:
    (1) 队列中的任务 会顺序执行
    (2) 让任务一个接一个的执行(一个任务执行完毕后,再执行下一个任务)

  • GCD内部是怎么实现的? (1) iOS和OS X的核心是XNU内核,GCD是基于XNU内核实现的
    (2) GCD的API全部在libdispatch库中
    (3) GCD底层实现主要有Dispatch Queue和Dispatch Source · Dispatch Queue: 管理block(操作) · Dispatch Source :处理事件(MACH端口发送,MACH端口接收,检测与进程相关事件等10种事件)

基本使用
  1. 全局队列与并行队列的区别
    (1) 不需要创建,直接GET就能用
    (2) 两个队列的执行效果相同
    (3) 全局队列没有名称,调试时,无法确认准确队列
    (4) 全局队列有高中默认优先级
  2. 全局队列 dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    3. 并行队列 dispatch_queue_t q = dispatch_queue_create("queuename", DISPATCH_QUEUE_CONCURRENT);
    4. 串行队列 dispatch_queue_t t = dispatch_queue_create("queuename",DISPATCH_QUEUE_SERIAL);
GCD高级:调度组dispatch_group
  • 应用场景: 开发的时候,有的时候出现多个网络请求都完成以后(每一个网络请求的事件长短不一定),再统一通知用户。比如: 下载小说:三国演义, 红楼梦, 水浒
      // 实例化一个调度组
      dispatch_group_t group = dispatch_group_create();
      
      // 队列
      dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
      
      // 任务添加到队列queue
      dispatch_group_async(group, queue, ^{
          NSLog(@"下载小说A---%@", [NSThread currentThread]);
      });
      
      dispatch_group_async(group, queue, ^{
          NSLog(@"下载小说B---%@", [NSThread currentThread]);
      });
      
      dispatch_group_async(group, queue, ^{
          NSLog(@"下载小说X---%@", [NSThread currentThread]);
      });
      
      // 获得所有调度组里面的异步任务完成的通知
      //    dispatch_group_notify(group, queue, ^{
      //        NSLog(@"下载完成,请观看%@", [NSThread currentThread]); // 异步的
      //    });
      
      //注意点: 在调度组完成通知里,可以跨队列通信
      dispatch_group_notify(group, dispatch_get_main_queue(), ^{
          // 更新UI,在主线程
          NSLog(@"下载完成,请观看%@", [NSThread currentThread]); // 异步的
      });
    复制代码
GCD高级:栅栏函数dispatch_barrier_(a)sync

首先是 Apple Documentation 对dispatch_barrier_async的解释

  • dispatch barrier允许开发者在一个并行队列中创造一个同步点,其实也是用于在竞争条件下资源的保护,防止同一资源同时被使用。 在并行异步任务中: barrier_sync前的任务并发执行,syncbarrier后的任务必须等syncbarrier中的任务执行完成之后才会执行他们,也会阻塞主线程的任务 barrier_async前的任务并发执行,barrier_async后的任务必须等barrier_async中的任务执行完成之后才会执行他们,但是Barrier不能阻塞主线程的任务

  • 作用如:打造线程安全的 NSMutableArray NSMutableArray本身是线程不安全的。简单来说,线程安全就是多个线程访问同一段代码,程序不会异常、不Crash。而编写线程安全的代码主要依靠线程同步。 1、不使用atomic修饰属性打造线程安全的原因: 1 ) atomic 的内存管理语义是原子性的,仅保证了属性的setter和getter方法是原子性的,是线程安全的,但是属性的其他方法,如数组添加/移除元素等并不是原子操作,所以不能保证属性是线程安全的。 2 ) atomic虽然保证了getter、setter方法线程安全,但是付出的代价很大,执行效率要比nonatomic慢很多倍(有说法是慢10-20倍)。 总之:使用nonatomic修饰NSMutableArray对象就可以了,而使用锁、dispatch_queue来保证NSMutableArray对象的线程安全。 2、打造线程安全的NSMutableArray 在《Effective Objective-C 2.0..》书中第41条:多用派发队列,少用同步锁中指出:使用“串行同步队列”(serial synchronization queue),将读取操作及写入操作都安排在同一个队列里,即可保证数据同步。而通过并发队列,结合GCD的栅栏块(barrier)来不仅实现数据同步线程安全,还比串行同步队列方式更高效。

几种任务嵌套:
  • 在主队列开启异步任务,不会开启新的线程而是依然在主线程中执行代码块中的代码。为什么不会阻塞线程?  主队列开启异步任务,虽然不会开启新的线程,但是他会把异步任务降低优先级,等闲着的时候,就会在主线程上执行异步任务。

  • 在主队列开启同步任务,为什么会阻塞线程?  在主队列开启同步任务,因为主队列是串行队列,里面的线程是有顺序的,先执行完一个线程才执行下一个线程,而主队列始终就只有一个主线程,主线程是不会执行完毕的,因为他是无限循环的,除非关闭应用程序。因此在主线程开启一个同步任务,同步任务会想抢占执行的资源,而主线程任务一直在执行某些操作,不肯放手。两个的优先级都很高,最终导致死锁,阻塞线程了。

  • 主线程队列注意: 下面代码执行顺序 1111 2222

- (void)main_queue_deadlock {
    dispatch_queue_t q = dispatch_get_main_queue();
    NSLog(@"1111");
    dispatch_async(q, ^{
        NSLog(@"主队列异步 %@", [NSThread currentThread]);
    });
    NSLog(@"2222");
    // 下面会造成线程死锁
//    dispatch_sync(q, ^{
//        NSLog(@"主队列同步 %@", [NSThread currentThread]);
//    });
}
复制代码
  • 串行队列开启异步任务后嵌套同步任务造成死锁
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(q, ^{
    NSLog(@"异步任务 %@", [NSThread currentThread]);
    // 下面开启同步造成死锁:因为串行队列中线程是有执行顺序的,需要等上面开启的异步任务执行完毕,才会执行下面开启的同步任务。而上面的异步任务还没执行完,要到下面的大括号才算执行完毕,而下面的同步任务已经在抢占资源了,就会发生死锁。
    dispatch_sync(q, ^{
        NSLog(@"同步任务 %@", [NSThread currentThread]);
    });
});
复制代码
  • 串行队列开启同步任务后嵌套同步任务造成死锁
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(q, ^{
  NSLog(@"同步任务 %@", [NSThread currentThread]);
  // 下面开启同步造成死锁:因为串行队列中线程是有执行顺序的,需要等上面开启的同步任务执行完毕,才会执行下面开启的同步任务。而上面的同步任务还没执行完,要到下面的大括号才算执行完毕,而下面的同步任务已经在抢占资源了,就会发生死锁。
  dispatch_sync(q, ^{
	 NSLog(@"同步任务 %@", [NSThread currentThread]);
  });
});
NSLog(@"同步任务 %@", [NSThread currentThread]);
复制代码
  • 串行队列开启同步任务后嵌套异步任务不造成死锁
  • 并行队列的任务嵌套例子
    dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    // 任务嵌套
    dispatch_sync(q, ^{
        NSLog(@"1 %@", [NSThread currentThread]);
        dispatch_sync(q, ^{
            NSLog(@"2 %@", [NSThread currentThread]);
            dispatch_sync(q, ^{
                NSLog(@"3 %@", [NSThread currentThread]);
            });
        });
        dispatch_async(q, ^{
            NSLog(@"4 %@", [NSThread currentThread]);
        });
        NSLog(@"5 %@", [NSThread currentThread]);
    });
// 运行结果是: 12345 或12354  
复制代码

并行队列里开启同步任务是有执行顺序的,只有异步才没有顺序
串行队列开启异步任务,是有顺序的

三、多线程-NSOperation:

  • 添加依赖
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 创建3个操作
    NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operationA---");
    }];
    NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operationB---");
    }];
    NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operationC---");
    }];
    
    // 添加依赖
    [c addDependency:a];
    [c addDependency:b];
    
    // 执行操作
    [queue addOperation:a];
    [queue addOperation:b];
    [queue addOperation:c];
复制代码
NSOperation queue

存放NSOperation的集合类。
(1) 用来存放NSOperation对象的队列,可以用来异步执行一些操作
(2) 一般可以用在网络请求等耗时操作
解释:操作和操作队列,基本可以看成java中的线程和线程池的概念。用于处理ios多线程开发的问题。网上部分资料提到一点是,虽然是queue,但是却并不是带有队列的概念,放入的操作并非是按照严格的先进现出。
这边又有个疑点是,对于队列来说,先进先出的概念是Afunc添加进队列,Bfunc紧跟着也进入队列,Afunc先执行这个是必然的,但是Bfunc是等Afunc完全操作完以后,B才开始启动并且执行,因此队列的概念离乱上有点违背了多线程处理这个概念。但是转念一想其实可以参考银行的取票和叫号系统。因此对于A比B先排队取票但是B率先执行完操作,我们亦然可以感性认为这还是一个队列。但是后来看到一票关于这操作队列话题的文章,其中有一句提到“因为两个操作提交的时间间隔很近,线程池中的线程,谁先启动是不定的。”瞬间觉得这个queue名字有点忽悠人了,还不如pool~综合一点,我们知道他可以比较大的用处在于可以帮组多线程编程就好了。

四、多线程-NSThread

  • 创建线程的方式
// 创建线程的方式1
- (void)createThread1 {
    // 创建线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:@"http://a.png"];
    thread.name = @"下载线程";
    
    // 启动线程(调用self的download方法)
    [thread start];
}
// 创建线程的方式2
- (void)createThread2 {
    [NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:@"http://b.jpg"];
}
// 创建线程的方式3
- (void)createThread3 {
    // 这2个不会创建线程,在当前线程中执行
    //    [self performSelector:@selector(download:) withObject:@"http://c.gif"];
    //    [self download:@"http://c.gif"];
    
    [self performSelectorInBackground:@selector(download:) withObject:@"http://c.gif"];
}
复制代码
  • 线程安全
-(void)threadSafe {
    self.leftTicketCount = 50;
    
    self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread1.name = @"1号窗口";
    
    self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread2.name = @"2号窗口";
    
    self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread3.name = @"3号窗口";
    
    [self threadSafeStart];
}
- (void)threadSafeStart {
    [self.thread1 start];
    [self.thread2 start];
    [self.thread3 start];
}
// 卖票
- (void)saleTicket {
    while (1) {
        // ()小括号里面放的是锁对象
        @synchronized(self) { // 开始加锁
            int count = self.leftTicketCount;
            if (count > 0) {
                [NSThread sleepForTimeInterval:0.05];
                
                self.leftTicketCount = count - 1;
                
                NSLog(@"%@卖了一张票, 剩余%d张票", [NSThread currentThread].name, self.leftTicketCount);
            } else {
                return; // 退出循环
            }
        } // 解锁
    }
}
复制代码

五、队列概念:

  • 主队列(线程)
    每一个应用程序都只有一个主线程。
    所有UI的更新工作,都必须在主线程上执行! 主线程是一直工作的,而且除非将程序杀掉,否则主线程的工作永远不会结束!

  • 队列和线程的区别:
    队列是管理线程的,相当于线程池,能管理线程什么时候执行。

  • 队列分为串行队列和并行队列:
    串行队列:队列中的线程按顺序执行(不会同时执行)
    并行队列:队列中的线程会并发执行,可能会有一个疑问,队列不是先进先出吗,如果后面的任务执行完了,怎么出去的了。这里需要强调下,任务执行完毕了,不一定出队列。只有前面的任务执行完了,才会出队列,也就是说你即使执行完毕了,也必须等前面的任务执行完毕出队列,才可以出去。

  • 主线程和工作线程的区别:
    主线程: 系统自动创建 能直操作UI 不能做耗时比较多的操作 自带自动释放池 RunLoop自动运行
    工作线程:程序猿手工创建 不能直接操作UI 写死循环都行 需要手工写自动释放池 手动运行

  • 主线程队列和GCD创建的队列区别: 主线程队列:主线程队列中不能开启同步,会阻塞主线程。只能开启异步任务,开启异步任务也不会开启新的线程,只是降低异步任务的优先级,让cpu空闲的时候才去调用。而同步任务,会抢占主线程的资源,会造成死锁。 GCD创建的队列:在GCD中创建的队列优先级没有主队列高,所以在GCD中的串行队列开启同步任务里面没有嵌套任务是不会阻塞主线程,只有一种可能导致死锁,就是串行队列里,嵌套开启任务,有可能会导致死锁。

  • 同步与异步的区别:  同步任务优先级高,在线程中有执行顺序,不会开启新的线程。 异步任务优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程。

六、多线程面试题:

  • 回到主线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 下载图片
    UIImage *image = nil;
    dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主线程
    });
    // [self performSelector:@selector(settingImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES modes:nil];
    // [self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:YES];
});
复制代码
  • 子线程的定时器: 代码片段原有的目的是异步延迟0.3秒后输出Hello world。但是运行之后发现不会输出Hello world。 原因是非主线程的NSRunLoop默认没有开启,而- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay; 函数内部是通过NSTimer定时器实现,在NSRunLoop没有开启的情况下,NSTimer不会得到正常运行。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
     [self performSelector:@selector(fireBlock:) withObject:^{  
        NSLog(@"hello world");  
     } afterDelay:0.3];  
 });  
复制代码
  • 主线程中执行代码
[self performSelectorOnMainThread: withObject: waitUntilDone:];
[self performSelector: onThread:[NSThread mainThread] withObject: waitUntilDone:];
dispatch_async(dispatch_get_main_queue(), ^{
});
复制代码
  • 延时执行
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,  (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){        
});
[self performSelector: withObject: afterDelay:];
[NSTimer scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:];
复制代码
  • GCD怎么用的? 全局队列异步操作,会新建多个子线程,操作无序执行,如果队列前有其他任务,会等待其他任务执行完毕在调用; 全局队列同步操作,不会新建线程,顺序执行; 主队列所有的操作都是主线程顺序执行,没有异步概念,主队列添加的同步操作永远不会执行,会死锁; 串行队列添加的同步操作会死锁,但是会执行嵌套同步操作之前的代码; 并行队列添加的同步操作不会死锁都在主线程执行; 全局队列添加的同步操作不会死锁; 同步操作 最主要的目的,阻塞并行队列任务的执行,只有当前的同步任务执行完毕之后,后边的任务才会执行,应用:用户登录。

  • 多线程安全怎么控制:

    1. 只在主线程刷新访问UI
    2. 如果要防止资源抢夺,得用synchronized进行加锁保护
    3. 如果异步操作要保证线程安全等问题,尽量使用GCD(有些函数默认就是安全的)
  • @synchronized @synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改。这个是objective-c的一个锁定令牌,防止self对象在同一时间内被其它线程访问,起到线程的保护作用。 一般在公用变量的时候使用,如单例模式或者操作类的static变量中使用。

  • NSLock 从微观上看一个线程在CPU上走走停停的,其运行状态可以分为(经典)三种:运行状态、就绪状态、阻塞。 两个线程同时访问一个共享的变量,一个对其加1、一个对其减1 由于内存的速度远低于CPU的速度,所以在设计CPU时通常不允许直接对内存中的数据进行运行。如果要运算内存中的数据通常是用一条CPU指令把内存中的数据读入CPU、再用另外一CPU指令对CPU中的数据做运算、最后再用一条CPU指令把结果写回内存。 i++ 读数据 运算 写数据 由于CPU在执行两条指令中间可以被打断,就可能导致另一个访问同样内存的线程运行,最终导致运算结果出错 解决办法就是加线程锁 NSLock lock方法 加锁:如果这个没被锁上则直接上锁、如果锁已经被其它线程锁上了当前线程就阻塞直至这个锁被其它线程解锁 unlock方法 解锁:解开这个锁,如果有其它线程因为等这个锁而进入了阻塞状态还要把那个线程变成就绪 用lock保护共享数据的原理 先上锁 访问共享的数据(临界区) 解锁

  • NSCondition 线程的同步,有一个经典的生产者消费者问题: 比如一个线程A负责下载数据 另一个线程B负责处理数据 还有一个线程C负责显示 解决方法就是用NSCondition: lock wait signal unlock 产生者线程(产生数据的线程),生产数据之后: lock signal(发出信号:如果有其它线程在等待这个信号就把那个线程变为就绪状态) unlock 消费者线程(处理或使用数据的线程): lock wait(等待信号:如果有线程发信号当前线程就会进入阻塞状态、直到有线程发出信号) unlock NSCondition有一个问题:假唤醒(wait的时候明明没有线程发信号,wait也可能返回),通常用一个表示状态的变量就能解决这个问题

  • 不可改变的对象,通常是线程安全的 线程安全的类和函数: NSArray, NSData, NSNumber..... 非线程安全: NSBundle, NSCoder, NSArchiver, NSMutableArray

  • 列举几种进程的同步机制,并比较其优缺点。 答案:原子操作、信号量机制、自旋锁、管程、会合、分布式系统

  • 进程之间通信的途径 答案:共享存储系统消息传递系统管道:以文件系统为基础

  • 进程死锁的原因 答案:资源竞争及进程推进顺序非法

  • 死锁的4个必要条件 答案:互斥、请求保持、不可剥夺、环路

  • 死锁的处理 答案:鸵鸟策略、预防策略、避免策略、检测与解除死锁

猜你喜欢

转载自juejin.im/post/5c9841cd5188252d5a14992a
今日推荐