iOS多线程总结

前言:本文章摘自作者devsongxx,链接:https://www.jianshu.com/p/8ff1eaebbc6e (著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。),从多线程的基本概念,进程的概念,引出iOS中的四种多线程方案pthread、NSThread、NSOperation和GCD,每一部分都有详细的代码和解释说明;在GCD中,引出同步、异步、串行队列(包括主队列)和并发队列概念,并对他们的六种组合进行详细的代码验证和说明,把这些概念安排的明明白白,然后详细的说明了GCD常见的其他用法;最后,对iOS中线程安全的方案进行全方面的介绍说明并且配上示例代码。

一、多线程概念

1、多线程概念:

  一个进程中可以开启多条线程,每条线程可以并行执行不同的任务。多线程可以充分的利用多个CPU同时处理任务,提高程序的执行效率。

2、进程概念:

  进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的。而线程是应用程序中一条任务的执行路径。

3、进程和线程的关系:

  1)一个进程可以包含多个线程;

  2)一个进程中的所有任务都是在线程中执行;

4、iOS多线程实现方案:

  1)pthread:纯C语言API,是一套通用的多线程API,适用于Linux、Unix、Windows等系统,线程生命周期由程序员管理。在iOS实际开发中,使用较少。

  2)NSThread:使用更加面向对象,并可直接操作线程对象,线程生命周期由程序员管理,项目开发中使用较多;

  3)NSOperation:基于GCD,使用更加面向对象,线程生命周期系统自动管理,使用较多;

  4)GCD:一套改进的C语言多线程API,能充分利用设备的多核优势,线程生命周期系统自动管理,使用最多;

二、pThread

  纯C语言API,使用较为麻烦。


图1

三、NSThread

1、NSThread的创建

  一个NSThread对象就代表一个线程,有三种方式创建:

  1)创建线程后需要start启动线程;

  2)创建线程后自动启动线程;

  3)隐式创建并启动线程;


图2

运行程序,打印结果:


图3

2、NSThread常用用法


图4

3、NSThread的线程通信

在开发中,我们经常会在子线程进行耗时操作,操作结束后再回到主线程去刷新 UI。这就涉及到了子线程和主线程之间的通信,常用两个API如下:


图5

模拟子线程下载,主线程刷新UI代码:


图6

程序运行打印结果:


图7

四、NSOperation

  NSOperation和NSOperationQueue是对GCD的一层封装,NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类NSInvocationOperation和NSBlockOperation。

1、NSInvocationOperation

  NSInvocationOperation代码:


图8

2、NSBlockOperation

NSBlockOperation代码:


图9

3、NSOperationQueue

  NSOperation可以调用start方法来执行任务,但默认是同步执行的,如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行操作。

  1)NSInvocationOperation与NSOperationQueue组合:


图10

2)NSBlockOperation与NSOperationQueue组合:


图11

运行结果:Thread1、Thread2、Thread3、Thread4都是子线程,所以添加到队列之后,是异步的。

Thread4=<NSThread:0x6000020d9c80>{number=6,name=(null)}

Thread2 = <NSThread:0x6000020a3140>{number=5,name=(null)}

Thread3 = <NSThread:0x6000020dcec0>{number=7,name=(null)}

Thread1 = <NSThread:0x6000020bc740>{number=4,name=(null)}

4、控制NSOperationQueue是串行队列还是并发队列

可以通过设置maxConcurrentOperationCount的值来选择串行队列还是并发队列。


图12


图13

5、NSOperation之间可以设置依赖来保证执行顺序

比如一定要让操作A执行完后,才能执行操作B,代码如下:


图14


图15

五、GCD

  Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法,它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统,它是一个在线程池模式的基础上执行的并行任务,GCD是一个替代诸如NSThread等技术的很高效和强大的技术。

  GCD 会自动利用更多的 CPU 内核(比如双核、四核),GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

  GCD两个核心概念,任务和队列,任务包括(同步执行任务、异步执行任务),队列包括(串行队列、并发队列),队列采用 FIFO(First In First Out)的原则,即先进先出原则。

1、同步执行、异步执行

      同步执行与异步执行的区别:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

  1)同步执行(sync):同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。只能在当前线程中执行任务,不具备开启新线程的能力

  2)异步执行(async):异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。可以在新的线程中执行任务,具备开启新线程的能力。

  注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。

2、串行队列、并发队列

  串行队列和并发队列的区别:执行顺序不同,以及开启线程数不同。

  1)串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(最多创建一个线程)。dispatch_get_main_queue() 主队列就是一个串行队列。

  2)并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行(可以开启多个线程)。dispatch_get_global_queue(0, 0) 全局队列就是并发队列。

  注意:并发队列的并发功能只有在异步(dispatch_async)方法下才有效。

3、经典各种组合模式

  本来同步、异步、串行、并发有四种组合,但是当前代码默认放在主队列中,全局并发队列可以作为普通并发队列来使用,所以主队列很有必要专门来研究一下,所以我们就有六种组合模式了。

  1)同步执行 + 串行队列:没有开启新线程,串行执行任务。


图16

2)同步执行 + 并发队列:没有开启新线程,串行执行任务。


图17

 3)异步执行 + 串行队列:有开启新线程(1条),串行执行任务。


图18

4)异步执行 + 并发队列:有开启新线程(多条),并发执行任务。


图19

5)同步执行+主队列:死锁卡住不执行。其实如果在串行队列中嵌套同步使用串行队列,也会发生死锁,原理和这个类似。所以项目中数据库处理FMDataQueue嵌套使用时,需要注意死锁问题。

  如果不是在主线程执行同步执行+主队列呢,那么不会发生死锁(读者可以自己代码测试验证)。


图20

运行结果:程序死锁崩溃,原因是默认viewDidLoad被添加到主队列中(先运行完viewDidLoad,后运行添加的任务),然后又同步添加Thread1任务到主队列中(先运行Thread1任务,后运行viewDidLoad任务),造成任务相互等待卡死,程序死锁崩溃。

6)异步执行+主队列:没有开启新线程,串行执行任务。


图21

4、GCD线程间的通信

在项目开发中,一般耗时的操作在别的线程处理,然后在主线程刷新UI,GCD线程间的通信如下:


图22

5、GCD其他的常见用法

  1)栅栏方法(dispatch_barrier_async)

  我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作,这就需要用到dispatch_barrier_async 方法在两个操作组间形成栅栏。NSOperation的 addDependency 也是这个效果。


图23

运行结果:先执行第一组任务(Thread1、Thread2),然后再执行栅栏的第二组任务(Thread3、Thread4、Thread5)。


图24

2)延时方法(dispatch_after)

项目中我们有时需要几秒之后再执行某个方法,那么可以使用GCD的延时方法,而不用创建一个定时器来处理。


图25

运行结果:先执行Thread1,5s之后执行Thread3,当前主队列执行完毕,然后执行dispatch_after添加的任务Thread2。至于为什么Thread3、Thread2时间接近,而不是相差2s,因为执行完Thread1的2s之后,Thread2添加到主队列,正在等待主队列执行完毕。


图26

3)执行一次(dispatch_once)

  项目开发中,我们经常使用单例模式,那么就对 dispatch_once 很熟悉,代码只运行一次。并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。


图27

4)快速迭代方法(dispatch_apply)

  通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部任务执行结束。如果是在串行队列中使用dispatch_apply,那么就和 for 循环一样,按顺序同步执行,所以实际使用的时候一般用并发队列。


图28

5)队列组(dispatch_group)

  有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务,这时候我们可以用到 GCD 的队列组。项目中常见使用场景有:请求多个接口数据返回后,再统一进行刷新;下载完多张图片后,再统一进行合并绘制等。

  调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合来实现 dispatch_group_async。

  调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。


图29


图30

运行结果:先异步执行group任务(Thread1、Thread2、Thread3),group执行完毕后,再执行nofity的任务。


图31

6)信号量(dispatch_semaphore)

  项目开发中,有时候有这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。比如说:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。

  GCD 中的信号量是指Dispatch Semaphore,是持有计数的信号。在Dispatch Semaphore中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,不等待,可通过。信号量主要用于:

  a、保持线程同步,将异步执行任务转换为同步执行任务;

  b、保证线程安全,为线程加锁;


图32

我们自己用信号量实现线程同步


图33

运行结果:semaphore初始创建时计数为 0,异步将Thread1任务添加到全局并发队列,不做等待,接着执行 dispatch_semaphore_wait 方法,semaphore 减 1,此时 semaphore == -1,当前线程进入等待状态。

  然后,异步任务 1 开始执行。任务 1 执行到 dispatch_semaphore_signal 之后,总信号量加 1,此时 semaphore == 0,正在被阻塞的线程(主线程)恢复继续执行,最后打印end。


图34

我们自己用信号量实现 semaphore 加锁


图35


图36

运行结果:ticketCount 按照顺序依次递减1,“火车票售完”打印三次是因为有三条线程运行。

  分析过程:首先三条线程都并发执行sellTickets方法,最快的一条线程首先执行dispatch_semaphore_wait,信号量减一,此时信号量为0,该条线程继续执行wait之后的代码,而其他较慢的两条线程进行等待新的信号量出现,较快的一条线程卖票之后,发送一个信号量,那么较慢的两条线程其中的一条执行wait之后的代码,如此循环,保证一个时间点只有一条线程进行卖票,从而保证线程安全。


图37

如果把信号量的代码注释,运行结果:剩余票数顺序明显不对,数据错乱。


图38

六、线程安全

  当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。线程安全问题:一般使用线程同步技术,同步技术有加锁、串行队列、信号量等。

  串行队列,上文已经有介绍,比如项目中FMDBQueue,就是在串行队列中同步操作数据库,从而保证线程安全。

  信号量,上文已经有介绍,其实类似于互斥锁。

  下面主要讲iOS中常见的锁:

  从大的方向讲有两种锁:自旋锁、互斥锁。

自旋锁:线程反复检查锁变量是否可用,是一种忙等状态,自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。自旋锁的性能高于互斥锁,因为响应速度快,但是可能发生优先级反转问题(如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁)。常见的自旋锁包括:OSSpinLock、atomic等。

互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁,因此适用于线程阻塞时间较长的场合。常见的互斥锁包括:os_unfair_lock、pthread_mutex、dispatch_semaphore、@synchronized,其中pthread_mutex又衍生出NSLock、NSCondition、NSConditionLock、NSRecursiveLock,更加面向对象的锁。

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

推荐阅读更多精彩内容

  • 一、 进程、线程、多线程 什么是进程 进程是指系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在...
    小猴子兵兵兵阅读 1,866评论 0 0
  • 类型简介实现语言线程生命周期pthreadposix接口,适合跨平台开发,使用难度较大c手动管理NSThread面...
    FengyunSky阅读 309评论 0 0
  • 用了这么久的GCD, 不总结一下实在良心上过不去. 有那么点白那啥的意思. 废话不多说. 走你 ⚔ 1 GCD介绍...
    lb_阅读 714评论 2 4
  • iOS中的常见多线程方案 GCD的常用函数 GCD中有2个用来执行任务的函数 用同步的方式执行任务dispatch...
    斑驳的流年无法释怀阅读 303评论 0 2
  • 进程与线程 进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个...
    朱敏_ITer阅读 337评论 0 1