美文网首页iOS面试
iOS 多线程详细描述

iOS 多线程详细描述

作者: 恋空K | 来源:发表于2019-08-26 18:03 被阅读0次

在 iOS 中其实目前有 4 套多线程方案,他们分别是:

Pthreads

NSThread

GCD

NSOperation & NSOperationQueue

NSThread

这套方案是经过苹果封装后的,并且完全面向对象的。所以你可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我创建并启动

先创建线程类,再启动

// 创建NSThread*thread = [[NSThreadalloc] initWithTarget:self selector:@selector(run:) object:nil];

// 启动[thread start];

[NSThread detachNewThreadSelector:@selector(run:)  toTarget:selfwithObject:nil];

[self performSelectorInBackground:@selector(run:) withObject:nil];

GCD

Grand Central Dispatch,听名字就霸气。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。所以基本上大家都使用GCD这套方案,老少咸宜,实在是居家旅行、杀人灭口,必备良药。不好意思,有点中二,咱们继续。

任务和队列

在 GCD 中,加入了两个非常重要的概念: 任务 和 队列

任务:即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行,他们之间的区别是 是否会创建新的线程。

同步执行只要是同步执行的任务,都会在当前线程执行,不会另开线程。

异步执行只要是异步执行的任务,都会另开线程,在别的线程执行。

这里说的并不准确,同步(sync)和异步(async)的主要区别在于会不会阻塞当前线程,直到Block中的任务执行完毕!

如果是同步(sync)操作,它会阻塞当前线程并等待Block中的任务执行完毕,然后当前线程才会继续往下运行。

如果是异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

队列:用于存放任务。一共有两种队列, 串行队列 和 并行队列

串行队列 中的任务会根据队列的定义 FIFO 的执行,一个接一个的先进先出的进行执行。

更新:放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

放到并行队列的任务,GCD 也会FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

创建队列

主队列:这是一个特殊的 串行队列。什么是主队列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。

自己创建的队列 其中第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列,可以为空。

自己可以创建串行队列, 也可以创建并行队列。看下面的代码(代码已更新),它有两个参数,第一个上面已经说了,第二个才是最重要的。

第二个参数用来表示创建的队列是串行的还是并行的,传入DISPATCH_QUEUE_SERIAL或NULL表示创建串行队列。传入DISPATCH_QUEUE_CONCURRENT表示创建并行队列。

//串行队列dispatch_queue_tqueue= dispatch_queue_create("tk.bourne.testQueue",NULL);

dispatch_queue_tqueue= dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);

//并行队列dispatch_queue_tqueue= dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

全局并行队列这应该是唯一一个并行队列, 只要是并行任务一般都加入到这个队列。这是系统提供的一个并发队列。

dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

创建任务

同步任务: 不会另开线程 改:会阻塞当前线程 (SYNC)

异步任务:会另开线程 改:不会阻塞当前线程 (ASYNC)

答案:

只会打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main} ,然后主线程就卡死了,你可以在界面上放一个按钮,你就会发现点不了了。

同步任务会阻塞当前线程,然后把 Block 中的任务放到指定的队列中执行,只有等到 Block 中的任务完成后才会让当前线程继续往下运行。

那么这里的步骤就是:打印完第一句后,dispatch_sync立即阻塞当前的主线程,然后把 Block 中的任务放到main_queue中,可是main_queue中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成,它不完成,dispatch_sync就会一直阻塞主线程,这就是死锁现象。导致主线程一直卡死。

>**分析:**我们按执行顺序一步步来哦:1.使用 `DISPATCH_QUEUE_SERIAL` 这个参数,创建了一个 **串行队列**。

2.打印出 `之前 - %@` 这句。

3.`dispatch_async` 异步执行,所以当前线程不会被阻塞,于是有了两条线程,一条当前线程继续往下打印出 `之后 - %@`这句, 另一台执行 Block 中的内容打印 `sync之前 - %@` 这句。因为这两条是并行的,所以打印的先后顺序无所谓。

4.注意,高潮来了。现在的情况和上一个例子一样了。`dispatch_sync`同步执行,于是它所在的线程会被阻塞,一直等到 `sync` 里的任务执行完才会继续往下。于是 `sync` 就高兴的把自己 Block 中的任务放到 `queue` 中,可谁想 `queue` 是一个串行队列,一次执行一个任务,所以 `sync` 的 Block 必须等到前一个任务执行完毕,可万万没想到的是 `queue` 正在执行的任务就是被 `sync` 阻塞了的那个。于是又发生了死锁。所以 `sync` 所在的线程被卡死了。剩下的两句代码自然不会打印。

### 队列组队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。下面是使用方法,这是一个很实用的功能。

多次使用队列组的方法执行任务, 只有异步方法

func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

这个方法重点是你传入的queue,当你传入的queue是通过DISPATCH_QUEUE_CONCURRENT参数自己创建的queue时,这个方法会阻塞这个queue注意是阻塞 queue ,而不是阻塞当前线程),一直等到这个queue中排在它前面的任务都执行完成后才会开始执行自己,自己执行完毕后,再会取消阻塞,使这个queue中排在它后面的任务继续执行。

如果你传入的是其他的queue, 那么它就和dispatch_async一样了。

func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

这个方法的使用和上一个一样,传入自定义的并发队列(DISPATCH_QUEUE_CONCURRENT),它和上一个方法一样的阻塞queue,不同的是 这个方法还会阻塞当前线程

如果你传入的是其他的queue, 那么它就和dispatch_sync一样了。

NSOperation和NSOperationQueue

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到NSOperation 和 NSOperationQueue分别对应 GCD 的任务 和 队列。操作步骤也很好理解:

将要执行的任务封装到一个NSOperation对象中。

将此任务添加到一个NSOperationQueue对象中。

添加任务

值得说明的是,NSOperation只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation和NSBlockOperation。创建一个 Operation 后,需要调用start方法来启动任务,它会默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其cancel方法即可。

NSInvocationOperation : 需要传入一个方法名。

//1.创建NSInvocationOperation对象

 NSInvocationOperation *operation =  [[NSInvocationOperationalloc]  initWithTarget:self selector:@selector(run) object:nil];

//2.开始执行[operation start];

NSBlockOperation

//1.创建NSBlockOperation对象 

NSBlockOperation*operation = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@", [NSThreadcurrentThread]);

 }];

//2.开始任务[operation start];

之前说过这样的任务,默认会在当前线程执行。但是NSBlockOperation还有一个方法:addExecutionBlock:,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务会并发执行,它会在主线程和其它的多个线程执行这些任务

NOTE:addExecutionBlock 方法必须在 start() 方法之前执行,否则就会报错:

创建队列

看过上面的内容就知道,我们可以调用一个NSOperation对象的start()方法来启动这个任务,但是这样做他们默认是同步执行的。就算是addExecutionBlock方法,也会在当前线程和其他线程中执行,也就是说还是会占用当前线程。这是就要用到队列NSOperationQueue了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法

这就是苹果封装的妙处,你不用管串行、并行、同步、异步这些名词。NSOperationQueue有一个参数maxConcurrentOperationCount最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为1的时候,他不就是串行了嘛!

NSOperationQueue还有一个添加任务的方法,- (void)addOperationWithBlock:(void (^)(void))block;,这是不是和 GCD 差不多?这样就可以添加一个任务到队列中了,十分方便。

NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:

[operation2 addDependency:operation1];//任务二依赖任务一[operation3 addDependency:operation2];//任务三依赖任务二

注意:不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。

可以使用 removeDependency 来解除依赖关系。

可以在不同的队列之间依赖,反正就是这个依赖是添加到任务身上的,和队列没关系。

相关文章

网友评论

    本文标题:iOS 多线程详细描述

    本文链接:https://www.haomeiwen.com/subject/ekddectx.html