本文主要内容

一.GCD相关
二.NSOperation相关
三.NSThread相关
四.多线程与锁

全方位剖析iOS高级技术问题(六)之多线程相关问题

一.GCD相关

  • 同步/异步和串行/并发
  • dispatch_barrier_async()
  • dispatch_group_async()

1.1、同步/异步和串行/并发

  • dispatch_sync(serial_queue,^{ // 使命}); // 同步串行
  • dispatch_async(serial_queue,^{ // 使命}); // 异步串行
  • dispatch_sync(concurrent_queue,^{ // 使命}); // 同步并行
  • dispatch_async(concurrent_queue,^{ // 使命}); // 异步并行

同步串行

问题一

- (void)viewDidLoad { // 使命1
    // 同步主行列中
    dispatch_sync(dispatch_get_main_queue(),^{ // 使命2
        [self doSomething];
    });
}
剖析:以上代码会发生死锁,同步分派到主行列的使命会发生由行列引起的循环等候。
iOS中默许会有一个主行列、主线程,主行列是一个串行行列,viewDidLoad办法在主行列傍边,能够看成使命1,在主线程中运转。Block相当于在主行列中添加的使命2dispatch_sync即同步阐明使命2也在主线程中运转,,使命1完结后才干履行使命2,可是使命1在履行过程中的某个时间就要开端履行使命2,使命2依靠于使命1的完结,使命1也依靠于使命2的完结,由此发生了彼此等候,导致死锁!

全方位剖析iOS高级技术问题(六)之多线程相关问题

  • 主列队提交的使命,不论经过同步办法仍是异步办法,都要在主线程中处理和履行;
  • iOS中默许有一个主行列和主线程,主行列是一个串行行列;
  • viewDidLoad在主行列中而且在主线程中运转。

问题二

- (void)viewDidLoad { // 使命1
    // 同步串行行列中
    dispatch_sync(serialQueue,^{ // 使命2
        [self doSomething];
    });
}
剖析:以上代码没有问题。
iOS默许有一个主行列和主线程,主行列是一个串行行列。viewDidLoad在主行列中能够看成使命1,在主线程中运转。Block是在另一个串行行列中,能够看成使命2dispatch_sync即同步阐明使命2也在主线程运转,因为二者不在同一个行列,不会发生死锁,可是使命2会延迟使命1的履行。

全方位剖析iOS高级技术问题(六)之多线程相关问题

同步并发

问题

- (void)viewDidLoad { // 使命1
    NSLog(@"1"); 
    // 同步大局并发行列
    dispatch_sync(global_queue,^{ // 使命2
        NSLog(@"2");
        dispatch_sync(global_queue,^{ // 使命3
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
剖析:输出12345。
iOS默许存在一个主行列和主线程,主行列是一个串行行列。viewDidLoad在主行列中,能够看成使命1(打印1),在主线程中运转。global_queue是大局并发行列,里边有使命2和使命3。dispatch_sync阐明global_queue中的使命也在主线程运转(会阻断线程,强制履行自己的)。因为global_queue和主线程行列不是同一个行列,不会形成死锁。因为global_queue是大局并发行列,一个使命不必管前面的使命是否履行完毕,所以使命2未完结时(打印2)就能够履行使命3(打印3),然后持续履行使命2(打印4、打印5)、使命1,而且都是在主线程履行。
  • 只要是同步提交的使命,不管提交到串行行列仍是并发行列,都是在当时线程履行;
  • 并发行列:提交到并发行列的一切使命或Block能够并发履行;

异步串行

- (void)viewDidLoad {
    // 异步主行列
    dispatch_async(dispatch_get_main_queue(),^{
        [self doSomething];
    });
}
剖析:iOS中默许有一个主行列和主线程,主行列是一个串行行列。viewDidLoad办法在主行列中,能够看成使命1,在主线程运转。Block相当于在主行列中添加使命2dispatch_async阐明使命2在子线程运转,也便是不会阻挡使命1的运转,使命1完结后才干履行使命2.
  • 异步会敞开新的线程;

问题

- (void)viewDidLoad {
    // 异步主行列
    dispatch_async(global_queue,^{
        NSLog(@"1");
        [self performSelector: @selector(printLog) withObject: nil, afterDelay: 0];
        NSLog(@"3");
    });
}
- (void)printLog { NSLog(@"2");}
剖析:输出成果为13global_queue是大局行列,选用dispatch_async,会拓荒一个子线程,实践上使命会在GCD底层所维护的线程池傍边某个线程中履行处理。子线程的runloop默许是不敞开的,而
经过异步办法分派使命到大局并发行列后,会在GCD底层所维护的线程池傍边某个线程中履行处理,在GCD底层所维护的线程池中的线程默许不会敞开对应的runloop,而performSelector:withObject:afterDelay是在没有runloop的情况下会失效,所以此办法不履行。

1.2、dispatch_barrier_async()

问题

怎样运用GCD完结多读单写?
剖析:需求满足读者和读者并发、读者和写者互斥、写者和写者互斥。
1.读处理之间需求并发的,用到并发行列,因为读取操作,往往需求立刻回来成果,故选用同步。这些读处理答应在对个子线程。2.写处理时其它操作都不能履行。运用栅门函数异步操作,原因是栅门函数同步操作会堵塞当时线程,假如当时线程还有其它操作,就会影响用户体会。
多读单写计划:运用GCD供给的栅门函数。
dispatch_barrier_async(concurrent_queue,^{ //写操作 });

关键代码

// 界说一个用户类UserCenter
#import "UserCenter.h"
@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];
    });
}
@end

全方位剖析iOS高级技术问题(六)之多线程相关问题

dispatch_barrier_sync和dispatch_barrier_async差异
共同点

  • 它们前面的使命先履行完,它们的使命履行完,再履行后边的使命;

不同点

  • dispatch_barrier_sync会堵塞当时线程,等它的使命履行完毕才干往下进行;
  • dispatch_barrier_async不会堵塞当时线程,答应其他非当时行列的使命持续履行。
  • 留意⚠️:运用栅门函数时,运用自界说行列才有含义,假如运用串行行列或体系的大局并发行列,栅门函数就相当于一个同步函数。

1.3、dispatch_group_async()

问题

运用GCD完结如下需求:AB、C三个使命并发,完结后履行使命D

全方位剖析iOS高级技术问题(六)之多线程相关问题

运用GCD中的dispatch_group_async()函数

关键代码

#import "GroupObject.h"
@interface GroupObject() {
    dispatch_queue_t concurrent_queue;
    NSMutableArray <NSURL *> *arrayURLs;
}
@end
@implementation GroupObject 
- (id)init {
    self = [super init];
    if (self) {
        // 创立并发行列
        concurrent_queue = dispatch_queue_create("concurrent_queue",DISPATCH_QUEUE_CONCURRENT);
        arrayURLs = [NSMutableArray array];
    }
    return self;
}
- (void)handle {
    // 创立一个group
    dispatch_group_t group = dispatch_group_create();
    // for循环遍历各个元素履行操作
    for (NSURL *url in arrayURLs) {
        // 异步组分派到并发行列傍边
        dispatch_group_async(group, concurrent_queue, ^{
            // 根据url去下载图片
            NSLog(@"url is %@",url);
        });
    }
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 当添加到组中的一切使命履行完结之后会调用该Block
        NSLog(@"一切图片已经悉数下载完结");
    });
}
@end

此示例实践中的应用场景为:同步并发下载多张图片,将这些图片合成为一张图片,需求确保多张图片下载完结后才干合成

二.NSOperation相关

需求和NSOperationQueue合作运用来完结多线程计划。

  • 添加使命依靠
  • 使命履行状况操控
  • 最大并发量

2.1、操控使命的状况类型

  • isReady:当时使命是否处于安排妥当状况;
  • isExecuting:当时使命是否正在履行;
  • isFinished:当时使命是否履行完结;
  • isCancelled:当时使命是否被符号为撤销(不是判别是否被撤销,是符号)
  • 经过KVO进行操控的。

2.2、状况操控

问题1:怎样操控NSOperation的状况?

  • 假如重写了main办法,底层操控使命履行状况以及使命推出;
  • 假如重写了start办法,自行操控使命状况,在合适的机遇修改对应的状况。

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

  • 经过KVO

小结 NSOperation:主行列默许在主线程履行,自界说行列默许在后台履行,会拓荒子线程。

三.NSThread相关

3.1、发动流程

  • 1.调用start()办法,发动线程;
  • 2.在start()内部会创立一个pthread线程,指定pthread线程的发动函数;
  • 3.在发动函数中会调用NSThread界说的main()函数;
  • 4.在main()函数中会调用performSelector:函数,来履行咱们创立的函数; // 常驻线程
  • 5.指定函数运转完结,会调用exit()函数,退出线程。

全方位剖析iOS高级技术问题(六)之多线程相关问题

四.对线程与锁相关

iOS傍边有哪些锁?

  • NSRecursiveLock
  • NSLock
  • dispatch_semaphore_t

4.1 @synchronized互斥锁

  • 一般在创立单例目标的时候运用,确保在多线程环境下,创立的单例目标是唯一的。

4.2 @atomic自旋锁

  • 特点关键字;
  • 对被润饰目标进行原子操作,不担任运用。

原子操作:不会被线程调度打断的操作。这种操作一旦开端,就一向运转到完毕,中间不会切换到另一个线程;
不担任运用:特点赋值时,能够确保线程安全;对特点进行操作,不能确保线程安全。

示例

@property(atomic) NSMutableArray *array;
self.array = [NSMutableArray array]; // 特点赋值,线程安全
[self.array addObject: obj];  // 特点操作,线程不安全

4.3 OSSpinLock自旋锁

  • 循环等候拜访,不开释当时资源。
  • 用于轻量级数据拜访,简略的int值+1/-1操作

运用场景

  • 内存引用计数加1或减1;
  • runtime也有运用到。

4.4 NSLock互斥锁和NSRecursiveLock递归

示例

- (void)methodA {
    [lock lock];
    [self methodB];
    [lock unlock];
}
- (void)methodB {
    [lock lock];
    // 操作逻辑
    [lock unlock];
}
`剖析`:如上代码会发生死锁。对同一把锁调用两次,因为重入的原因形成死锁。
`处理计划`:运用递归锁,能够重入。
- (void)methodA {
    [recursiveLock lock];
    [self methodB];
    [recursiveLock unlock];
}
- (void)methodB {
    [recursiveLock lock];
    // 操作逻辑
    [recursiveLock unlock];
}

4.5 dispatch_semaphore_t信号量

触及的函数

  • dispatch_semaphore_create(5)
    创立信号量,指定最大并发数
struct semaphore {
    int value;
    List <thread>;
}
  • dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)
    等候信号量>=1;假如当时信号量>=1,信号量减1,程序持续履行;假如信号量<=0,原地等候,不答应程序持续履行。
dispatch_semaphore_wait() {
    S.value = S.value - 1;
    // 堵塞是一个主动行为
    if S.value < 0 then Block(S.list); 
}
  • dispatch_semaphore_signal(semaphore)
    程序履行完毕,发送信号,开释资源,信号量加1。
dispatch_semaphore_singal() {
    S.value = S.value + 1;
    // 唤醒是一个被动行为
    if S.value <= 0 then wakeup(S.list); 
}    
小结
1.锁分为互斥锁和自旋锁;
2.互斥锁和自旋锁的差异:
    自旋锁:忙等候,即在拜访被锁资源时,调用者线程不会休眠,而是不停循环企图拜访,直到被锁资源开释。
    互斥锁:会休眠,即在拜访被锁资源时,调用者线程会休眠,此刻CPU能够调度其他线程作业,直到被锁资源开释,此刻会唤醒休眠线程。

本文总结

基础小结

  • 行列概念:行列是使命的容器;

  • 串行行列、并发行列的差异
    串行行列
    1.一次只是调度一个使命,行列中的使命一个个履行(一个使命完结后,再运转下一个使命);遵从FIFO准则:先进先出,后进后出。
    2.串行行列使命之间彼此包含,容易形成(行列)死锁。
    并发行列
    1.不需求把一个使命完结后,再运转下一个使命;遵从FIFO准则,常识不需求等候使命完结;
    2.并发行列不会形成死锁; 3.只要并发行列+异步,才会有多线程的作用。只要当时一条线程能够运用,并发行列中使命尽管能够快速取出分派,可是只要一条线程(主线程),也只能一个个排队履行。

  • 并行、并发的差异
    并行:同一时间,多条指令在多个处理器一起履行;
    并发:同一时间,只能处理一条指令,可是多个指令被快速的轮换履行,到达了具有一起履行的作用。

  • 异步和同步的差异
    异步:能够敞开新的线程;
    同步:不能够敞开新的线程,在当时线程运转。

  • 同步异步、串行并行形象理解
    串行行列
    使命一个个履行,后边使命需求等候前面使命完结才干履行!

    全方位剖析iOS高级技术问题(六)之多线程相关问题

并行行列
使命一个个履行,不必等候前面是否完结!

全方位剖析iOS高级技术问题(六)之多线程相关问题

  • 行列中履行使命的作业场景形象描绘
    1.从容器(行列)中取出使命(履行代码),放到传送带(线程)上。假如容器是串行行列,则完结一个,取出一个;假如容器是并发行列,则一向不停的投进。
    2.使命(履行代码)放到传送带(线程)的一刹那,CPU(操作工)看了一眼上面的标签(同步或异步)。假如标签是同步,就将它放到当时传送带;假如标签是异步,就新添加一条传送带,然后把使命放上去。

问题1:怎样用GCD完结多读单写?

即对dispatch_barrier_async()的运用,详见上文。

问题2:iOS体系为咱们供给了几种多线程技能?各自的特点是什么?

  • GCD 用于简略的线程同步,子线程分派,处理如多读单写的问题,

  • NSOperation和NS OperationQueue
    便利操控使命状况、操控添加依靠和移除依靠。可用于杂乱线程操控,如第三方AFNetWorking和SDWebImage。

  • NSThread
    用于完结常驻线程(经过在main函数中调用performSelector:函数创立自己的函数)。

问题3:NSOperation目标在Finished之后是怎样从queue傍边移除掉的?

NSOperation目标在Finished之后,会在内部以KVO的办法告诉对应的NSOperationQueue,到达对NSOperation目标进行移除的目的。

问题4:运用过哪些锁?怎么运用的?

@synchronized互斥锁 @atomic自旋锁 OSSpinLock自旋锁 NSLock互斥锁和NSRecursiveLock递归锁

有任何问题,欢迎各位谈论指出!觉得博主写的还不错的费事点个赞喽