iOS多线程基础

系列文章:

多线程

多线程 pthread、NSThread

多线程 GCD

多线程 NSOperation

多线程运用

  • 原理
  • 优缺点
  • 主线程
  • 面试题:
    • iOS NSThread NSOperation GCD的优缺点
    • NSOperation 相比于 GCD 有哪些优势?
    • 你的项目什么时候选择使用GCD,什么时候选择NSOperation?
    • 线程间怎么通信?
  • Pthreads
  • NSThread
  • GCD
  • NSOperation和NSOperationQueue
  • 多线程运用
  • 线程的状态
  • 多线程的安全隐患
    • 互斥锁 优点与缺点
    • atomic与nonatomic

多线程

多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

  • 注意:多线程并发,并不是cpu在同一时刻同时执行多个任务,只是CPU调度足够快,造成的假象。

优点

  • 能适当提高程序的执行效率
  • 能适当提高资源利用率(CPU、内存利用率)

缺点

  • 1.开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 2.线程越多,CPU在调度线程上的开销就越大

主线程

一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”。

作用:

显示\刷新UI界面

处理UI事件(比如点击事件、滚动事件、拖拽事件等)

注意:

刷新UI必须放在主线程

别将比较耗时的操作放到主线程中

耗时操作会卡住主线程,严重影响UI的流畅度

多线程实现方案:
[图片上传失败...(image-dd619f-1517394470696)]

面试题: iOS NSThread NSOperation GCD的优缺点

NSThread、 GCD、 NSOperation 抽象封装度层次从低到高,抽象封装度越高使用越简单。

NSthread:

优点:比其他两种轻量级。

缺点:需要自己管理线程的生命周期,线程同步。 线程同步对数据的加锁会有一定的开销。

用来做调试 一个方法查看代码楚楚的线程

Operation、GCD:

优点:不需要关心线程管理,数据同步的事情。

两者区别:NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。

当需求能够以更简单的底层代码完成的时候,GCD或许是个更好的选择,因为GCD主要与block结合使用,代码简洁高效和集中,维护度比较高。GCD 关注如何在多个 cpu 上提升效率,GCD在追求性能的底层操作来说,是速度最快的。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD更有优势

原文链接

面试题: NSOperation 相比于 GCD 有哪些优势?

1.从性能上来说:
GCD是底层的C语言构成的API,更接近底层,而NSOperationQueue基于GCD,使用Objective-C实现的面向对象的线程管理,比GCD更高级抽象。所以GCD在追求性能的底层操作来说,是速度最快的。(单纯从性能速度来说,只有Instruments显示有真正的性能提升时才有必要用低级的GCD。)

2.从异步操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
对同一个并行队列中的任务操作来说:
在NSOperationQueue中,可以随时取消已经设定要准备执行的任务,能够方便地设置依赖关系,能够设置NSOperation的priority优先级,而GCD需要自己写更多的代码来实现。

3.如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperationQueue会是一个更好的选择。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势

4.从复用度来说:
能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。
NSOperation NSOperation 是一个抽象基类,我们必须使用它的子类。iOS 提供了两种默认实现:NSInvocationOperation 和 NSBlockOperation。

原文链接

面试题: 你的项目什么时候选择使用GCD,什么时候选择NSOperation?

答:项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会使代码更为易读,建议在简单项目中使用。

原文链接

面试题: 线程间怎么通信?

(1)GCD:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
//下载图片
UIImage *image = nil;
dispatch_async(dispatch_get_main_queue(),^{
//回到主线程
});

(2)NSThread的线程通信

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
//下载图片
UIImage *image = nil;
[self performSelector:@selector(settingImage:) onThread:[NSThread mainThread]withObject:image waitUntilDone:YES modes:nil];
}

这种情况也适用于子线程之间的通信。

在 iOS 中的 4 套多线程方案,他们分别是:

  • Pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueue

Pthreads

NSThread

多线程 pthread、NSThread

  • Pthreads
  • NSThread
    • 创建并启动
      • 先创建线程类,再启动
      • 创建并自动启动线程
      • 使用 performSelector 的方法创建(隐式创建)并自动启动线程
    • 其他方法

GCD

多线程 GCD

  • 任务和队列
    • 任务:执行什么操作
    • 队列:用来存放任务
    • 同步、异步、并发、串行 表格
  • 创建任务
    • 同步任务
    • 异步任务
  • 创建队列
    • 使用dispatch_get_main_queue()获取主队列
    • 使用dispatch_queue_create函数创建队列 (串行队列, 并行队列)
    • 使用dispatch_get_global_queue获取全局并发队列
  • GCD运用
    • 一、线程间通信
      • performSelector方法 (在主线程上执行操作
        、在指定线程上执行操作)
      • GCD方法 dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程,执行UI刷新操作}); });
    • 二、延时执行
      • performSelector: withObject: afterDelay:
      • GCD dispatch_after
    • 三、一次性代码 单例模式 dispatch_once
    • 四、快速迭代 dispatch_apply
    • 五、队列组 dispatch_group_async
    • 六、GCD 计时器应用
    • 七、栅栏 dispatch_barrier
    • 死锁面试题
  • GCD总结
    • 同步主队列死锁原因
    • 同步串行队列不会发生死锁原因

NSOperation和NSOperationQueue

多线程 NSOperation

  • NSOperation相对于GCD:
  • NSOperation剖析
  • 添加任务
    • NSInvocationOperation
    • NSBlockOperation
    • 自定义子类继承NSOperation
  • 创建队列
    • 主队列
    • 其他队列
    • 添加队列 添加依赖
  • 其他方法

多线程运用

多线程运用

  • 线程同步
    • 互斥锁 @synchronized(self) {//需要执行的代码块}
    • 同步执行
      • GCD dispatch_sync
      • NSOperation & NSOperationQueue
  • 延迟执行
    • performSelector: withObject: afterDelay:
    • GCD dispatch_after
    • NSTimer
  • 单例模式
  • 从其他线程回到主线程的方法
    • NSThread performSelectorOnMainThread
    • GCD dispatch_async(dispatch_get_main_queue(), ^{ });
    • NSOperationQueue [[NSOperationQueue mainQueue] addOperationWithBlock:^{}];

线程的状态

image.png

多线程的安全隐患

一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源。
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。

image.png

解决办法:(互斥锁)


image.png
// 注意:锁定1份代码只用1把锁,用多把锁是无效的
@synchronized(锁对象) {
// 需要锁定的代码
};

如果给数据加了锁,就等于将这些异步的子线程变成同步的了,这也叫做线程同步技术。

优点与缺点

能有效防止因多线程抢夺资源造成的数据安全问题

需要消耗大量的CPU资源

  • Critical Section (临界区)
    简而言之就是两个或多个线程不能同时执行一段代码去操作一个共享的资源
  • Race Condition (竞态条件)
    这种情况是指基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,
    竞态条件可导致无法预测的行为,例如程序的并发任务执行的确切顺序
  • Deadlock (死锁)
    所谓的死锁是指两个(或多个)线程都卡住了,都在等待对方完成后执行,
    第一个不能完成是因为在等待第二个的完成,但第二个也不能完成,
    是因为它在等待第一个完成

atomic与nonatomic

OC在定义属性时有nonatomic和atomic两种选择

@property(nonatomic,copy)NSString*name;

@property(atomic,copy)NSString*name;

atomic:原子属性,为setter方法加锁(默认就是atomic)
线程安全,需要消耗大量的资源

nonatomic:非原子属性,不会为setter方法加锁
非线程安全,适合内存小的移动设备

开发建议

所有属性都声明为nonatomic

尽量避免多线程抢夺同一块资源

尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

推荐阅读更多精彩内容