iOS 多线程总结(上)

一、前言

多线程是在 iOS 里非常重要的一块儿知识点,我最近学习了李明杰大神的多线程相关视频,对自己的多线程相关知识进行了查缺补漏,受益良多,在此根据所学进行简单的记录,希望能帮助更多伙伴,也能作为自己模糊了以后查阅使用。
本篇文章分为上下两篇。

二、iOS中常见的多线程方案

技术方案 简介 语言 线程生命周期 使用频率
pthread ☑ 一套通用的多线程API
☑ 适用于Unix\Linux\Windows等系统
☑ 跨平台\可移植
☑ 使用难度大
C 程序员管理 几乎不用
NSThread ☑ 使用更加面向对象
☑ 简单易用,可直接操作线程对象
OC 程序员管理 偶尔使用
GCD ☑ 旨在替代NSThread等线程技术
☑ 充分利用设备的多核
C 自动管理 \color{red}{经常使用}
NSOperation ☑ 基于GCD(底层是GCD)
☑ 比GCD多了一些更简单实用的功能
☑ 使用更加面向对象
OC 自动管理 \color{red}{经常使用}

二、同步、异步、串行、并发

1、GCD 中有 2 个用来执行任务的函数

  • 用同步的方式执行任务
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    queue:队列
    block:任务

2、GCD 的队列可以分为 2 大类型

  • \color{red}{并发}队列(Concurrent Dispatch Queue)
    ☑ 可以让多个任务\color{blue}{并发}(同时)执行(自动开启多个线程同时执行任务)
    \color{blue}{并发}功能只有在\color{blue}{异步}(dispatch_\color{blue}{async})函数下才有效

  • \color{red}{串行}队列(Serial Dispatch Queue)
    ☑ 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

  • 用异步的方式执行任务
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

  • GCD 源码:https://github.com/apple/swift-corelibs-libdispatch

3、容易混淆的术语

  • 4 个术语比较容易混淆:\color{blue}{同步、异步}\color{red}{并发、串行}

  • \color{blue}{同步}\color{blue}{异步}主要影响:能不能开启新的线程
    \color{blue}{同步}:在\color{green}{当前}线程中执行任务,\color{green}{不具备}开启新线程的能力
    \color{blue}{异步}:在\color{green}{新的}线程中执行任务,\color{green}{具备}开启新线程的能力

  • 并发和串行主要影响:任务的执行方式
    \color{red}{并发}\color{blue}{多个}任务并发(同时)执行
    \color{red}{串行}\color{blue}{一个}任务执行完毕后,再执行下一个任务

dispatch_sync\color{red}{是立马在当前线程执行完里面的任务。}
dispatch_sync 和 dispatch_async 用来控制是否要开启新的线程。

队列的类型,决定了任务的执行方法(并发、串行)
1、并发队列
2、串行队列
3、主队列(也是一个串行队列)

4、各种队列的执行效果:

并发队列 手动创建的串行队列 主队列
同步( sync ) \color{red}{没有}开启新线程
\color{blue}{串行}执行任务
\color{red}{没有}开启新线程
\color{blue}{串行}执行任务
\color{red}{没有}开启新线程
\color{blue}{串行}执行任务
异步( async ) \color{green}{有}开启新线程
\color{orange}{并发}执行任务
\color{green}{有}开启新线程
\color{blue}{串行}执行任务
\color{red}{没有}开启新线程
\color{blue}{串行}执行任务
  • 使用 sync 函数往\color{red}{当前}\color{blue}{串行}队列中添加任务,会卡住当前的串行队列(产生死锁)

5、问题

问题1:

以下代码是在主线程执行的,会不会产生死锁?

- (void)viewDidLoad {
  [super viewDidLoad];
  NSLog(@"执行任务1");
  dispatch_queue_t queue = dispatch_get_main_queue();
  dispatch_sync(queue, ^{
    NSLog(@"执行任务2");
  });
  NSLog(@"执行任务3");
}

答案:会!执行顺序是1崩溃。
分析:
1、队列的特点:排队,FIFO(Fisrst In First Out,先进先出)
2、dispatch_sync 同步队列特点:立马在当前线程执行任务,执行完毕才能继续往下执行。
因为任务2是在同步执行,并且在主队列中。而viewDidLoad也是在主队列中。
所以主队列中的任务2会等viewDidLoad这个任务先执行完再执行,而viewDidLoad这个任务需要执行完任务3才算执行完。所以就出现了死锁。

如下图所示:
问题2:

以下代码是在主线程执行的,会不会产生死锁?(和第1题的区别只是把sync改为了async)

- (void)viewDidLoad {
  [super viewDidLoad];
  NSLog(@"执行任务1");
  dispatch_queue_t queue = dispatch_get_main_queue();
  dispatch_async(queue, ^{
    NSLog(@"执行任务2");
  });
  NSLog(@"执行任务3");
}

答案:不会!执行顺序是132。
分析:异步虽然因为是主队列不会开启新线程,但因为是异步的,所以不是必须马上取出任务2执行再执行后面的任务3,所以可以最后再取出任务2执行。

问题3:

以下代码是在主线程执行的,会不会产生死锁?

- (void)viewDidLoad {
  [super viewDidLoad];
  NSLog(@"执行任务1");

  dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
  dispatch_async(queue, ^{ // Block 0
    NSLog(@"执行任务2");

    dispatch_sync(queue, ^{  // Block 1
      NSLog(@"执行任务3");
    });

    NSLog(@"执行任务4");
  });
  NSLog(@"执行任务5");
}

答案:会!执行顺序是152崩溃。
分析:dispatch_sync\color{red}{是立马在当前线程执行完里面的任务。}
代码中注释标明了 \color{blue}{Block 0}\color{green}{Block 1}
要想执行完\color{blue}{Block 0},必须执行完\color{green}{Block 1}(因为是sync)才能执行后面的任务4,才算执行完\color{blue}{Block 0}。但要想执行\color{green}{Block 1},又必须先执行队列里先进入的\color{blue}{Block 0}(FIFO)。\color{blue}{Block 0}\color{green}{Block 1}互相等待,所以造成了死锁。

问题4:

以下代码是在主线程执行的,会不会产生死锁?

- (void)viewDidLoad {
  [super viewDidLoad];
  NSLog(@"执行任务1");

  dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myqueue2", DISPATCH_QUEUE_CONCURRENT);
  dispatch_async(queue, ^{ // Block 0
    NSLog(@"执行任务2");

    dispatch_sync(queue2, ^{  // Block 1
      NSLog(@"执行任务3");
    });

    NSLog(@"执行任务4");
  });
  NSLog(@"执行任务5");
}

答案:不会!执行顺序是15234。
分析:因为\color{blue}{Block 0}\color{green}{Block 1}所处两个不同队列。就算这道题两个都是串行队列,也不会产生死锁。

问题5:

以下代码是在主线程执行的,会不会产生死锁?

- (void)viewDidLoad {
  [super viewDidLoad];
  NSLog(@"执行任务1");

  dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
  dispatch_async(queue, ^{ // Block 0
    NSLog(@"执行任务2");

    dispatch_sync(queue, ^{  // Block 1
      NSLog(@"执行任务3");
    });
 
    NSLog(@"执行任务4");
  });
  NSLog(@"执行任务5");
}

答案:不会!执行顺序是15234。
分析:因为\color{blue}{Block 0}\color{green}{Block 1}处在并发队列,并发队列不会阻塞。

  • 总结:
    使用 sync 函数往\color{red}{当前}\color{blue}{串行}队列中添加任务,会卡住当前的串行队列(产生死锁)
    死锁 的产生两个条件:
    1、是 sync 同步的;
    2、往\color{red}{当前}的\color{blue}{串行}队列添加任务;

6、多线程的安全隐患

  • 资源共享
    ☑ 1 块资源可能会被多个线程共享,也就是\color{red}{多个线程可能会访问同一块资源}
    ☑ 比如多个线程访问同一个对象、同一个变量、同一个文件。

  • 当多个线程访问同一块资源时,很容易引发\color{red}{数据错乱和数据安全}问题

7、面试题

面试题1:

请问下面代码的打印结果是什么?


打印结果是:1、3
原因:
1、performSelector:withObject:afterDelay: 的本质是往 Runloop 中添加定时器
2、子线程默认没有启动 Runloop
如果换成performSelector:withObject则打印结果为 132,这个不带 Delay 的方法不是 runloop 的,相当于直接调用。

runloop 的代码是没有开源的,所以看不到 afterDelay 的实现,如果想了解,可以看 GNUstep

  • GNUstep
    GNUstep 是 GNU 计划的项目之一,它将 Cocoa 的 OC 库重新开源实现了一遍。
    源码地址:http://www.gnustep.org/resources/downloads.php
    虽然 GNUstep 不是苹果官方源码,但还是具有一定的参考价值。
面试题2:

请问下面代码的打印结果是什么?


打印结果是:1 崩溃
分析:在执行 performSelector 的时候,目标线程已经退出了,所以崩溃了。可以在block中开启runloop:

[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

这样就会打印 12 了。因为里面启动了一个 runloop,执行完打印 1 以后线程并没有死,而是 runloop 休眠了,之后 performSelector 唤醒runloop去执行test。

以上的总结参考了并部分摘抄了以下文章,非常感谢以下作者的分享!:
小马哥-李明杰的《多线程》课程

转载请备注原文出处,不得用于商业传播——凡几多

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 了解多线程,首先我们需要了解以下知识 进程 ●进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们...
    赵哥窟阅读 89评论 0 0
  • 前言:本文章摘自作者devsongxx,链接:https://www.jianshu.com/p/8ff1eaeb...
    贾小敏1234阅读 467评论 0 0
  • 目录 简述 NSThread GCD操作与队列异步操作并行队列同步操作并行队列同步操作串行队列异步操作串行队列队列...
    鱼王00阅读 484评论 0 2
  • 用了这么久的GCD, 不总结一下实在良心上过不去. 有那么点白那啥的意思. 废话不多说. 走你 ⚔ 1 GCD介绍...
    lb_阅读 707评论 2 4
  • 多线程 优缺点,实际应用多线程比较 死锁:向同一个/当前的串行队列添加同步sync操作任务,会产生死锁,新等旧,旧...
    愤斗的小蚂蚁阅读 998评论 0 4