多线程相关知识点

图1 知识结构

一、知识结构分析

整体知识架构

备注:
从上到下,更加面向对象,也就更容易使用。

多线程之间的关系

  • pthread是POSIX线程的API

  • NSThread是Cocoa对pthread的封装

  • GCD和NSOperationQueue是基于队列的并发API

  • GCD是基于pthread和queue实现的

  • NSOperationQueue是对GCD的高级封装

  • GCD

  • NSOperation
    AFNetWorking + SDWebImage框架多有使用

问题1:AFNetWorking为什么使用NSOperation,为什么不使用GCD?

解释:
此道题目也可以理解为NSOperation比GCD的有点。
1、NSOperation是基于GCD更高一层的封装,完全面向对象;比GCD更加简单易用,代码可读性高。
2、 FIFO队列,而NSOperationQueue中的队列可以被重新设置优先级,从而实现不同操作的执行顺序调整。CGD不具备
3、添加操作之间的依赖关系,方便的控制执行顺序;GCD不具备。
4、可以很方便的取消一个操作的执行;CGD不具备。
5、使用 KVO 观察对操作执行状态的更改;CGD不具备。

  • NSThread
    用于实现一个常驻线程

问题2:常驻线程有什么作用?

解释:
通常情况下,创建子线程,在里面执行任务,任务完成后,子线程会立刻销毁;如果需要经常子线程中操作任务,那么频繁的创建和销毁子线程会造成资源的浪费。
所以需要常驻线程。

  • 多线程和锁
    线程同步和资源共享

二、GCD

主要结构如下:

  • 同步/异步、串行/并发
  • dispatch_barrier_async
  • dispatch_group

2.1、同步/异步、串行/并发

  • dispatch_sync(serial_queue,^{ //任务 });

  • dispatch_async(serial_queue,^{ //任务 });

  • dispatch_sync(concurrent_queue,^{ //任务 })

  • dispatch_async(concurrent_queue,^{ //任务 })

2.1.1、同步串行

问题3:主队列同步

图2 主队列同步

解释:
队列引起的循环等待,不是线程引起的循环等待。
详细解释如下:

图3 同步串行

1、ios中默认会有一个主队列、主线程。
2、主队列是一个串行队列。
3、viewDidLoad在主队列中,可以看成一个任务1。
4、Block相当于在主队列添加任务2。
5、viewDidLoad在主线程运行。
6、dispatch_sync说明任务2也在主线程运行。
7、任务1完成后才能执行任务2。
但是任务1还没有完成,就开始执行任务2,任务2有依赖任务1的完成,任务1依赖任务2的完成,造成死锁

问题4:主队列异步

没有问题

解释:
虽然没有问题,但是主队列提交的任务,无论通过同步/异步方式,都要在主线程进行处理!!!

问题5:串行队列同步

图4 串行队列同步

解释:
不会有问题。
详细解释如下:

图3 串行队列

1、iOS默认会有一个主队列、主线程。
2、主队列是一个串行队列。
3、viewDidLoad在主队列中,可以看成一个任务1。
4、Block是在另一个串行队列中,可以看成任务2。
5、viewDidLoad在主线程运行。
6、dispatch_sync说明任务2也在主线程运行。
7、因为二者不是在同一个队列,不会存在死锁,但是任务2会延迟任务1执行。

2.2.2、同步并发

问题6:下面代码输出结果是:

图4 同步并发

解释
1、iOS默认会有一个主队列、主线程。
2、主队列是一个串行队列。
3、viewDidLoad在主队列中,可以看成一个任务1。
4、global_queue是全局并发队列,里面有任务2和任务3
5、viewDidLoad在主线程运行。
6、dispatch_sync说明global_queue中的任务也在主线程运行(会阻断线程,强制执行自己的)。
7、因为global_queue和主线程队列不是同一个队列,不会造成死锁。
8、因为global_queue是全局并发队列,一个任务不用管前面的任务是否执行完毕。所以任务2未完成时,可以执行任务3,然后执行任务2,都是在主线程执行。

2.2.3、异步串行

图5 异步串行

这段代码是经常使用的
代码分析:
1、ios中默认会有一个主队列、主线程。
2、主队列是一个串行队列。
3、viewDidLoad在主队列中,可以看成一个任务1。
4、Block相当于在主队列添加任务2。
5、viewDidLoad在主线程运行。
6、dispatch_async说明任务2在子线程运行,也就是不会阻挡任务1的运行。
7、任务1完成后才能执行任务2。
因为任务1在子线程运行,不会阻挡任务2,所以正常使用。

2.2.4、异步并发

问题7:以下代码输出结果:

图6 异步并发

解释
1、global_queue是全局队列,采用dispatch_async,所以会开辟一个子线程。
2、子线程的runLoop默认是不开启的,而performSelector:withObject:afterDelay是在没有runloop的情况下会失效,所以此方法不执行。
3、打印结果13。

2.3、dispatch_barrier_async()

2.3.1、场景

问题8:怎样利用CGD实现多读单写?

利用CGD提供的栅栏函数
解析:

多读单写模型

  • 读者、读者并发
  • 读者、写者互斥
  • 写者、写者互斥

可以理解为:
1、读处理之间是并发的,肯定要用并发队列
因为读取操作,往往需要立刻返回结果,故采用同步
这些读处理允许在多个子线程。
2、写处理时候,其余操作都不能执行。利用栅栏函数,异步操作。利用栅栏函数异步操作的原因:栅栏函数同步操作会阻塞当前线程,如果当前线程还有其它操作,则会影响用户体验。

核心代码如下:

@interface UserCenter()
{
    // 定义一个并发队列
    dispatch_queue_t concurrent_queue;
    
    // 用户数据中心, 可能多个线程需要数据访问
    NSMutableDictionary *userCenterDic;
}

@end

// 多读单写模型
@implementation UserCenter

- (id)init
{
    self = [super init];
    if (self) {
        // 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
        concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 创建数据容器
        userCenterDic = [NSMutableDictionary dictionary];
    }
    
    return self;
}

- (id)objectForKey:(NSString *)key
{
    __block id obj;
    // 同步读取指定数据
    dispatch_sync(concurrent_queue, ^{
        obj = [userCenterDic objectForKey:key];
    });
    return obj;
}

- (void)setObject:(id)obj forKey:(NSString *)key
{
    // 异步栅栏调用设置数据
    dispatch_barrier_async(concurrent_queue, ^{
        [userCenterDic setObject:obj forKey:key];
    });
}

2.3.2、dispatch_barrier_sync和dispatch_barrier_async区别

共同点:

  • 它们前面的任务先执行完。
  • 它们的任务执行完,再执行后面的任务。

不同点:

  • dispatch_barrier_sync会阻止当前线程,等它的任务执行完毕,才能往下进行。
  • dispatch_barrier_async不会阻塞当前线程,允许其它非当前队列的任务继续执行。

注意:
使用栅栏函数时,使用自定义队列才有意义,如果使用串行队列/系统的全局并发队列,这个栅栏函数就相当于一个同步函数

2.3、dispatch_group

问题9:使用CGD实现这个需求:A、B、C三个任务并发,完成后执行任务D。

 // 创建一个group
 dispatch_group_t group = dispatch_group_create();
 // 异步组分派到并发队列当中
  dispatch_group_async(group, concurrent_queue, ^{
  });
  //监听  
  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      // 当添加到组中的所有任务执行完成之后会调用该Block
 });

三、NSOperation

3.1、NSOperation优点

  • 添加任务依赖
  • 任务执行状态控制
  • 最大并发量(maxConcurrentOperationCount)

问题10:我们可以控制任务的哪些状态?

  • isReady
    当前任务是否处于就绪状态
  • isExecuting
    当前任务是否正在执行
  • isFinished
    当前任务是否执行完成
  • isCancelled
    当前任务是否被标记为取消(不是判断是否被取消,是标记)
    是通过KVO进行控制的

3.2、状态控制

问题11:我们怎么控制NSOperation的状态

  • 如果只重写了main方法,底层控制任务执行状态以及任务退出。
  • 如果重写了start方法,自行控制任务状态。

问题12:系统是怎样移除一个isFinished=YES的NSOperation的?

  • 通过KVO

小结:
NSOperation: 主队列默认在主线程执行,自定义队列默认在后台执行(会开辟子线程)。

四、NSThread

4.1、启动流程

启动流程

1、调用start()方法、启动线程。
2、在start()内部会创建一个pthread线程,指定pthread线程的启动函数。
3、在启动函数中会调用NSThread定义的main()函数。
4、在main()函数中会调用performSelector:函数,来执行我们创建的函数。
5、指定函数运行完成,会调用exit()函数,退出线程。

4.2、常驻线程

参考RunLoop

五、多线程与锁

问题13:iOS中都有哪些锁,你是怎样使用的?

解释:

5.1、@synchronized(互斥锁) 🌟🌟🌟

  • 一般在创建单例对象的时候使用,保证在多线程环境下,创建的单例对象是唯一的。

5.2、 atomic(自旋锁)🌟🌟🌟

  • 属性关键字
  • 对被修饰对象进行原子操作(不负责使用)
    备注:

原子操作:不会被线程调度打断的操作;这种操作一旦开始,就一直运行到结束,中间不会切换到另一个线程。

不负责使用:属性赋值时候,能够保证线程安全;对属性进行操作,不能保证线程安全。
例如:
@property (atomic) NSMutableArray *array;
self.array = [NSMutableArray array];//线程安全
[self.array addObject:obj];//线程不安全

5.3、 OSSpinLock(自旋锁)

  • 循环等待访问,不释放当前资源。
  • 用于轻量级数据访问,简单的int值 +1/-1操作。
  • 使用场景:
    • 内存引用计数加1或减1
    • runtime也有使用到。

5.4、 NSLock(互斥锁)

蚂蚁金服面试题:

NSLock面试题

解释:
对同一把锁两次调用,由于重入的原因会造成死锁;解决办法就是使用递归锁(可以重入)。

5.5、 NSRecursiveLock(递归锁)(互斥锁)

NSRecursiveLock锁

5.6、 dispatch_semaphore_t(信号量)🌟🌟🌟

  • dispatch_semaphore_create(5)
    创建信号量,指定最大并发数
  • dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
    等待信号量>=1。
    如果当前信号量>=1,信号量减1,程序继续执行。
    如果信号量<=0,原地等待,不允许程序往下执行。
  • dispatch_semaphore_signal(semaphore)
    程序执行完毕,发送信号,释放资源,信号量加1。
5.6.1、dispatch_semaphore_create
dispatch_semaphore_create
5.6.2、dispatch_semaphore_wait
dispatch_semaphore_wait
5.6.3、dispatch_semaphore_signal
dispatch_semaphore_signal

小结:
1、锁分为互斥锁自旋锁
2、互斥锁和自旋锁的区别
自旋锁: 忙等待。即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放
互斥锁: 会休眠。即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其它线程工作,直到被锁资源释放,此时会唤醒休眠线程。

问题14:iOS系统为我们提供了几种多线程技术?各自有什么特点?

解释:

  • GCD
    用于一些简单的线程同步,包括子线程分派;还有就是解决类似于多读单写功能。

  • NSOperation及NSOperationQueue
    可以方便控制任务状态、添加依赖/移除依赖;多用于复杂线程控制:AFNetWorking和SDWebImage。

  • NSThread
    往往用于实现一个常驻线程。

总结:

概念理解

  • 队列概念
    队列是任务的容器

  • 串行队列、并发队列区别
    串行队列: 一次仅仅调度一个任务,队列中的任务一个个执行。(一个任务完成后,再运行下一个任务)。
    遵循FIFO原则:先进先出、后进后出

    并发队列:不需要把一个任务完成后,再运行下一个任务。
    仍然遵循FIFO原则,只是不需要等待任务完成。

  • 并行、并发区别
    并行:同一时刻,多条指令在多个处理器同时执行。
    并发:同一个时刻,只能处理一条指令,但是多个指令被快速的轮换执行,达到了具有同时执行的效果。

  • 异步、同步区别
    异步: 可以开启新的线程。
    同步: 不可以开启新的线程,在当前线程运行。

同步异步、串行并行形象理解

这两对概念单独看起来,明白怎么回事;但是,一旦运用起来,总是不能得心应手。总的来说,就是不能将概念熟记于心,缺乏形象概念。

下面采用图解 + 文字进行表述:

串行队列图解
并行队列图解

一个队列(串行+并发)好比一个容器
执行代码好比一个个任务
同步异步好比任务的标签
容器里面装有好多个打有标签任务
线程好比流水线的传送带
所有的工作都是CPU在做,姑且将CPU比做操作工

代码运行的时候,大家想象工厂的流水线的工作场景:
1、从容器(队列)中取出任务(执行代码),放到传送带上。
如果容器是串行队列,则完成一个,取出一个。
如果容器是并发队列,则一直不停的投放。
2、任务(执行代码)放到传送带(线程)的一刹那,CPU(操作工)看了一眼上面的标签:如果标签是同步,就将它放到当前传送带;如果标签是异步,就新增加一条传送带,然后把任务放上去(理解操作工无所不能,可以随意增加传送带)。

上面只是一个形象的比喻,加深对多线程理解。

小结:
从上面的分析可知:
1、串行队列任务之间相互包含,容易造成死锁;并发队列则不会。这种死锁称为队列死锁。
2、并发队列+异步,才会有多线程效果。
如果只有当前一个线程可以利用,并发队列中任务虽然可以快速取出分派,奈何只有一个线程(主干道),只能一个个排队执行。

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

推荐阅读更多精彩内容