[关闭]
@qidiandasheng 2022-08-07T21:46:39.000000Z 字数 4623 阅读 4368

iOS多线程之串行队列、并发队列、同步执行、异步执行

iOS实战


任务和队列

导出图片Sun Jun 28 2020 21_27_27 GMT+0800 (中国标准时间).png-184.1kB

9045053-e02fba0a02bd489d.png-29.7kB

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

任务:即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行。

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

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

如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。一般会开其他线程来运行任务。


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

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

并发队列中的任务根据同步或异步有不同的执行方式。

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

同步方式 全局并发队列 手动创建的串行队列 主队列
同步(sync) 没有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务
异步(async) 有开启新的线程;并行执行任务 有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务

队列与任务的组合

串行队列同步执行

不会创建新线程,任务一个一个按顺序执行。

  1. dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue",
  2. DISPATCH_QUEUE_SERIAL);
  3. for (int i = 0; i<10; i++) {
  4. dispatch_sync(serialQueue, ^{
  5. sleep(1);
  6. NSLog(@"%@ 执行任务%d current theard:%@",[NSDate date],i,[NSThread currentThread]);
  7. });
  8. }

截屏2020-06-26 上午11.36.08.png-350.8kB

并发队列同步执行

不用等待一个任务执行完毕才开始调度下一个任务,但是同步任务不会开启新的线程,所以也是一个一个的执行。

  1. dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent_queue",
  2. DISPATCH_QUEUE_CONCURRENT);
  3. for (int i = 0; i<10; i++) {
  4. dispatch_sync(concurrentQueue, ^{
  5. sleep(1);
  6. NSLog(@"%@ 执行任务%d current theard:%@",[NSDate date],i,[NSThread currentThread]);
  7. });
  8. }

截屏2020-06-26 上午11.38.49.png-347.9kB

可以看到,程序没有并发执行,而且没有开启新线程,是在主线程执行的。 有没有觉得奇怪呢?为什么向并发队列添加的任务,没有开启新线程,而是在主线程执行的? 如下解释:

  1. 使用dispatch_sync 添加同步任务,必须等添加的block执行完成之后才返回。
  2. 既然要执行block,肯定需要线程,要么新开线程执行,要么再已存在的线程(包括当前线程)执行。
  3. dispatch_sync的官方注释里面有这么一句话:
  4. As an optimization, dispatch_sync() invokes the block on the current thread when possible.
  5. 作为优化,如果可能,直接在当前线程调用这个block
  6. 所以一般在大多数情况下,通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行。
  7. 上面我们添加的任务的代码是在主线程,所以就直接在主线程执行了。

串行队列异步执行

按顺序来调度任务,任务会创建一个新的线程来执行。

  1. dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue",
  2. DISPATCH_QUEUE_CONCURRENT);
  3. for (int i = 0; i<10; i++) {
  4. dispatch_async(serialQueue, ^{
  5. sleep(1);
  6. NSLog(@"%@ 执行任务%d current theard:%@",[NSDate date],i,[NSThread currentThread]);
  7. });
  8. }

截屏2020-06-26 上午11.42.57.png-354kB

使用dispatch_async函数添加到serial dispatch queue中的任务,一般会(不一定)新开一个线程,但是不同的异步任务用的是同一个线程。

并发队列异步执行

会创建多个线程,操作并发无序执行。

  1. dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent_queue",
  2. DISPATCH_QUEUE_CONCURRENT);
  3. for (int i = 0; i<10; i++) {
  4. dispatch_async(concurrentQueue, ^{
  5. sleep(1);
  6. NSLog(@"%@ 执行任务%d current theard:%@",[NSDate date],i,[NSThread currentThread]);
  7. });
  8. }

截屏2020-06-26 上午11.44.29.png-354.2kB

全局队列同步执行

操作不会新建线程,操作顺序执行。

  1. dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2. for (int i = 0; i<10; i++) {
  3. dispatch_sync(globalQueue, ^{
  4. sleep(1);
  5. NSLog(@"%@ 执行任务%d current theard:%@",[NSDate date],i,[NSThread currentThread]);
  6. });
  7. }

截屏2020-06-26 上午11.46.37.png-345.8kB

全局队列异步执行

创建多个线程,操作无序执行。

  1. dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2. for (int i = 0; i<10; i++) {
  3. dispatch_async(globalQueue, ^{
  4. sleep(1);
  5. NSLog(@"%@ 执行任务%d current theard:%@",[NSDate date],i,[NSThread currentThread]);
  6. });
  7. }

截屏2020-06-26 上午11.48.04.png-357kB

主线程同步执行

主线程不会结束,主队列中的同步操作永远不会被执行,会死锁。

viewDidLoad算是一个完整的任务,而block里面算第二个任务,主队列的顺序是[viewDidLoadblock]。所以block要等viewDidLoad执行完才能执行,但是viewDidLoad里又有一个同步任务block需要执行完才能执行下去。这样就造成了互相等待,最终形成死锁。

  1. - (void)viewDidLoad {
  2. NSLog(@"任务1");
  3. dispatch_sync(dispatch_get_main_queue(), ^{
  4. NSLog(@"%@ 执行任务2 current theard:%@",[NSDate date],[NSThread currentThread]);
  5. });
  6. NSLog(@"任务1");
  7. }

截屏2020-06-26 上午11.51.24.png-147kB

主线程主队列异步执行

  1. - (void)viewDidLoad {
  2. NSLog(@"任务1");
  3. dispatch_async(dispatch_get_main_queue(), ^{
  4. NSLog(@"%@ 执行任务2 current theard:%@",[NSDate date],[NSThread currentThread]);
  5. });
  6. dispatch_async(dispatch_get_main_queue(), ^{
  7. NSLog(@"%@ 执行任务3 current theard:%@",[NSDate date],[NSThread currentThread]);
  8. });
  9. dispatch_async(dispatch_get_main_queue(), ^{
  10. NSLog(@"%@ 执行任务4 current theard:%@",[NSDate date],[NSThread currentThread]);
  11. });
  12. dispatch_async(dispatch_get_main_queue(), ^{
  13. NSLog(@"%@ 执行任务5 current theard:%@",[NSDate date],[NSThread currentThread]);
  14. });
  15. NSLog(@"任务6");
  16. }
  1. test[96860:10845080] 任务1
  2. test[96860:10845080] 任务6
  3. test[96860:10845080] 2021-03-18 03:24:05 +0000 执行任务2 current theard:<NSThread: 0x600000fe0180>{number = 1, name = main}
  4. test[96860:10845080] 2021-03-18 03:24:05 +0000 执行任务3 current theard:<NSThread: 0x600000fe0180>{number = 1, name = main}
  5. test[96860:10845080] 2021-03-18 03:24:05 +0000 执行任务4 current theard:<NSThread: 0x600000fe0180>{number = 1, name = main}
  6. test[96860:10845080] 2021-03-18 03:24:05 +0000 执行任务5 current theard:<NSThread: 0x600000fe0180>{number = 1, name = main}

参考

iOS多线程 串行队列、并发队列以及同步执行、异步执行

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注