iOS多线程

2020-07-09  本文已影响0人  丶天空蓝丶

OC中的多线程

OC中多线程根据封装程度可以分为三个:NSThread、GCD和NSOperation,本文主要讲的是GCD的使用。

一: NSThread

NSThread是封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大;因为需要自己手动控制线程的生命周期,比较麻烦,因此用的比较少。

1.1、 简单使用
 //创建线程
   NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:@"Thread"];
   //或者
   NSThread  *newThread=[[NSThread alloc] init];
   NSThread  *newThread= [[NSThread alloc]initWithBlock:^{
       NSLog(@"initWithBlock");
   }];
//设置优先级
newThread.qualityOfService = NSQualityOfServiceUserInteractive;
//启动线程
[newThread start];
//取消线程
[newThread cancel];
1.2、实用方法

尽管NSThread大部分功能用起来比较麻烦,但是有一些小方法还是比较方便使用的,也是我会在项目中用到的

//获取当前线程
[NSThread currentThread];
//获取主线程
[NSThread mainThread];
//睡眠当前线程 5秒
 [NSThread sleepForTimeInterval:5];
//5秒后执行run方法
[self performSelector:@selector(run) withObject:nil afterDelay:5.0];
//回到主线程执行run方法
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];

二:NSOperation

NSOperation是基于GCD的一个抽象基类,是GCD的封装,属于object-c类。将线程封装成要执行的操作,不需要管理线程的生命周期和同步,比GCD可控性更强,例如可以加入操作依赖(addDependency)、设置操作队列最大可并发执行的操作个数(setMaxConcurrentOperationCount)、取消操作(cancel)等。

NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作,分别是使用子类 NSInvocationOperation、NSBlockOperation或者自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作

2.1、子类NSInvocationOperation使用
// 1.创建 NSInvocationOperation 对象
   NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task) object:nil];
   // 2.调用 start 方法开始执行操作
   [op start];
//执行的方法
- (void)task {
    NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}

当前线程同步执行,直接 [self task]不香吗

2.2子类NSBlockOperation使用
//创建 NSBlockOperation 对象
 NSBlockOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
     NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}];
[blkOperation start];

当前线程同步执行,但是NSBlockOperation可以添加并发。

//添加额外的操作
[op addExecutionBlock:^{
     [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
     NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}];

当并发数量大于1时,就会自动开启新线程,多写一个addExecutionBlock即多添加一个并发。另:查看网上并没有说这种并发的上限,但经过我的测试,并发上限跟当前机器的核数有关,即6核最多同时开启6个并发,8核8个。

2.3自定义继承自 NSOperation 的子类使用(略)
2.4NSOperation最强大用法--创建队列(NSOperationQueue)的使用

NSOperationQueue 一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发功能。下边是主队列、自定义队列的基本创建方法和特点。

// 主队列获取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];
// 自定义队列创建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
将操作加入到队列中

上边我们说到 NSOperation 需要配合 NSOperationQueue 来实现多线程。那么我们需要将创建好的操作加入到队列中去。总共有两种方法:

 // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //2.设置最大并发操作数
    queue.maxConcurrentOperationCount = 5;
    
    // 3.创建操作
    // 使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    
    // 使用 NSInvocationOperation 创建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

// 4.使用 addOperation: 添加所有操作到队列中
    [queue addOperation:op1]; 
    [queue addOperation:op2]; 

此时就会开启异线程并发执行,最大并发数为maxConcurrentOperationCount设置的值,maxConcurrentOperationCount最大值为64,需要注意的是多条线程是高效,但也并不是越多越好,线程多了会更耗CPU,所以我们需要控制线程最大并发数。

// 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  
    //2.设置最大并发操作数
    queue.maxConcurrentOperationCount = 2;
    
    // 3.使用 addOperationWithBlock: 添加操作到队列中
    [queue addOperationWithBlock:^{
         [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
         NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }];
    [queue addOperationWithBlock:^{
         [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
         NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
    }];

三:GCD

这是官方对其介绍


GCD

翻译:Dispatch,也称为Grand Central Dispatch(GCD),包含语言功能、运行时库和系统增强功能,这些功能为支持macOS、iOS、watchOS和tvOS中的多核硬件上的并发代码执行提供了系统的、全面的改进。
官方文档地址

3.1、GCD 任务

任务就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:同步任务和异步任务。两者的主要区别是:是否需要等待队列的任务执行结束,以及是否具备开启新线程的能力

3.2、GCD 队列

队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在 GCD 中有两种队列:串行队列和并发队列,两者的主要区别是:执行顺序不同,以及开启线程数不同

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

串行队列.png 并发队列.png
3.3、GCD 的使用

GCD 的使用步骤其实很简单,只有两步:

3.4、队列的创建

可以用dispatch_queue_create来创建,包含两个参数

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("identification", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("identification", DISPATCH_QUEUE_CONCURRENT);

其中串行队列又有一个队列叫主队列

// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();

并发队列又有一个队列叫全局并发队列

// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3.5、任务的创建

GCD的任务分两种,同步任务和异步任务分别用dispatch_sync和dispatch_async创建

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

根据队列和任务的不同,我们可以将GCD的使用分为下面几种组合

1.同步任务 + 串行队列
2.异步任务 + 串行队列
3.同步任务 + 并发队列
4.异步任务 + 并发队列
上面我们又说了串行队列和并发队列各有一种特殊用法,分别是主队列和全局并发队列;其中全局并发队列和并发队列用法基本一致,将放在一起讨论了;主队列则很有必要专门来研究一下,所以我们就多了两种组合方式:
5.同步任务 + 主队列
6.异步任务 + 主队列

3.6、 任务和队列不同组合方式的区别

我们先来考虑最基本的使用,不同队列+不同任务 简单组合使用的不同区别。暂时不考虑队列中嵌套队列的这种复杂情况。(注:因为主队列+同步在主线程和其他线程中结果差异很大,会简单区分讨论下,其他暂时默认都是在主线程的环境下调用)

不同队列+不同任务组合的区别

区别 串行队列 并发队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 主线程:死锁卡住
其他线程:没有开启新
线程,串行执行任务
异步(async) 有开启新线程(1条),串行执行任务 有开启新线程,并发执行任务 没有开启新线程,串行执行任务
a、同步任务 + 串行队列
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"同步串行---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("sky.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 2
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 3
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"同步串行---end");

输出结果:
2020-07-10 14:18:45.873274+0800 TestOC[11204:603832] currentThread---<NSThread: 0x600001140000>{number = 1, name = main}

2020-07-10 14:18:45.873401+0800 TestOC[11204:603832] 同步串行---begin
2020-07-10 14:18:47.873699+0800 TestOC[11204:603832] 1---<NSThread: 0x600001140000>{number = 1, name = main}
2020-07-10 14:18:49.874353+0800 TestOC[11204:603832] 2---<NSThread: 0x600001140000>{number = 1, name = main}
2020-07-10 14:18:51.875848+0800 TestOC[11204:603832] 3---<NSThread: 0x600001140000>{number = 1, name = main}
2020-07-10 14:18:51.876233+0800 TestOC[11204:603832] 同步串行---end

小结:

我们可以发现:

b、异步任务 + 串行队列
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"异步串行---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("sky.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        // 追加任务 1
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 2
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 3
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"异步串行---end");

输出结果:
2020-07-10 14:28:08.908495+0800 TestOC[11272:609791] currentThread---<NSThread: 0x600002940540>{number = 1, name = main}
2020-07-10 14:28:08.908612+0800 TestOC[11272:609791] 异步串行---begin
2020-07-10 14:28:08.908720+0800 TestOC[11272:609791] 异步串行---end
2020-07-10 14:28:10.913096+0800 TestOC[11272:609937] 1---<NSThread: 0x600002935380>{number = 6, name = (null)}
2020-07-10 14:28:12.917224+0800 TestOC[11272:609937] 2---<NSThread: 0x600002935380>{number = 6, name = (null)}
2020-07-10 14:28:14.918440+0800 TestOC[11272:609937] 3---<NSThread: 0x600002935380>{number = 6, name = (null)}

小结:

我们可以发现:

c、同步任务 + 并发队列
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"同步并发---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("sky.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 2
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 3
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"同步并发---end");

输出结果:
2020-07-10 14:35:32.679831+0800 TestOC[11350:614865] currentThread---<NSThread: 0x600000574380>{number = 1, name = main}
2020-07-10 14:35:32.679967+0800 TestOC[11350:614865] 同步并发---begin
2020-07-10 14:35:34.680461+0800 TestOC[11350:614865] 1---<NSThread: 0x600000574380>{number = 1, name = main}
2020-07-10 14:35:36.681217+0800 TestOC[11350:614865] 2---<NSThread: 0x600000574380>{number = 1, name = main}
2020-07-10 14:35:38.682705+0800 TestOC[11350:614865] 3---<NSThread: 0x600000574380>{number = 1, name = main}

2020-07-10 14:35:38.682950+0800 TestOC[11350:614865] 同步并发---end

小结:

我们可以发现:

d、异步任务 + 并发队列
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"异步并发---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("sky.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务 1
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 2
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 3
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"异步并发---end");

输出结果:
2020-07-10 14:57:08.983092+0800 TestOC[11438:626363] currentThread---<NSThread: 0x600000674100>{number = 1, name = main}
2020-07-10 14:57:08.983220+0800 TestOC[11438:626363] 异步并发---begin
2020-07-10 14:57:08.983308+0800 TestOC[11438:626363] 异步并发---end
2020-07-10 14:57:10.986914+0800 TestOC[11438:626468] 3---<NSThread: 0x600000606c40>{number = 7, name = (null)}
2020-07-10 14:57:10.986917+0800 TestOC[11438:626473] 1---<NSThread: 0x600000653a80>{number = 3, name = (null)}
2020-07-10 14:57:10.986984+0800 TestOC[11438:626493] 2---<NSThread: 0x600000634240>{number = 6, name = (null)}

小结:

我们可以发现:

e、同步任务 + 主队列
在主线程中执行
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"同步主队列---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 2
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 3
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"同步主队列---end");

输出结果:
2020-07-10 15:52:05.332135+0800 TestOC[11586:652198] currentThread---<NSThread: 0x600000f882c0>{number = 1, name = main}
2020-07-10 15:52:05.332254+0800 TestOC[11586:652198] 同步主队列---begin
(lldb)
卡在第一个dispatch_sync(queue, ^{ 地方报错

主队列.png
小结:

我们可以发现:

在其他线程中调用中执行
// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务
    [NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
-(void)syncMain {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"同步主队列---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 2
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 3
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"同步主队列---end");
    
}

输出结果:
2020-07-10 16:34:17.706859+0800 TestOC[11706:672887] currentThread---<NSThread: 0x600002ab3a80>{number = 6, name = (null)}
2020-07-10 16:34:17.707026+0800 TestOC[11706:672887] 同步主队列---begin
2020-07-10 16:34:19.718782+0800 TestOC[11706:672720] 1---<NSThread: 0x600002acc100>{number = 1, name = main}
2020-07-10 16:34:21.719807+0800 TestOC[11706:672720] 2---<NSThread: 0x600002acc100>{number = 1, name = main}
2020-07-10 16:34:23.721612+0800 TestOC[11706:672720] 3---<NSThread: 0x600002acc100>{number = 1, name = main}
2020-07-10 16:34:23.722060+0800 TestOC[11706:672887] 同步主队列---end

小结:

我们可以发现:

为什么在其他线程中不会死锁呢?
因为当前队列在当前线程中,而任务 1、任务 2、任务3 都在追加到主队列时,会自动放在主线程的队列中,因此当前队列中不存在其他任务,可以执行完毕,然后去执行任务1,然后任务2、任务3。所以这里不会卡住线程,也就不会造成死锁问题。

f、异步任务 + 主队列
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"异步主队列---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 追加任务 1
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 2
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 3
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"异步主队列---end");

输出结果:
2020-07-10 16:50:43.285808+0800 TestOC[11891:683408] currentThread---<NSThread: 0x6000014702c0>{number = 1, name = main}
2020-07-10 16:50:43.285926+0800 TestOC[11891:683408] 异步主队列---begin
2020-07-10 16:50:43.286015+0800 TestOC[11891:683408] 异步主队列---end
2020-07-10 16:50:45.296442+0800 TestOC[11891:683408] 1---<NSThread: 0x6000014702c0>{number = 1, name = main}
2020-07-10 16:50:47.297820+0800 TestOC[11891:683408] 2---<NSThread: 0x6000014702c0>{number = 1, name = main}
2020-07-10 16:50:49.299448+0800 TestOC[11891:683408] 3---<NSThread: 0x6000014702c0>{number = 1, name = main}

小结:

我们可以发现:

3.7、GCD 嵌套队列

嵌套队列也就是在队列中镶嵌队列,也是线程间的通信。
线程间的通信主要就是队列中嵌套队列。感觉上面3.6里面同步任务 + 并发队列、同步任务 + 串行队列 并没有多少用到的地方,且意义不大,就不多做介绍了。

嵌套队列之队列里面嵌套主队列

在 iOS 开发过程中,我们一般需要在主线程里边进行 UI 刷新。而我们又会把一些耗时操作放在其他线程里面,如:图片下载、文件上传等耗时操作。而当我们在其他线程完成了耗时操作时,需要回到主线程UI 刷新,那么就用到了线程之间的通讯。场景模拟,如:在一个UITableView列表上点击上传实现文件上传,文件上传后刷新列表将上传按钮改成已上传等操作。

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"---begin");
    
    // 获取并发队列
    dispatch_queue_t queue = dispatch_queue_create("sky.testQueue", DISPATCH_QUEUE_CONCURRENT);
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 异步追加任务 1
        sleep(2);                                       // 模拟耗时操作,如:文件上传
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            sleep(1);                                       // UI刷新
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });
    
    dispatch_async(queue, ^{
        // 异步追加任务 2
        sleep(4);                                       // 模拟耗时操作,如:文件上传
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            sleep(2);                                       // UI刷新
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });

    NSLog(@"---end");

输出结果:
2020-07-13 13:36:54.413184+0800 TestOC[14492:913025] currentThread---<NSThread: 0x6000018c4300>{number = 1, name = main}
2020-07-13 13:36:54.413276+0800 TestOC[14492:913025] ---begin
2020-07-13 13:36:54.413394+0800 TestOC[14492:913025] ---end
2020-07-13 13:36:56.418424+0800 TestOC[14492:913135] 1---<NSThread: 0x6000018882c0>{number = 5, name = (null)}
2020-07-13 13:36:57.419698+0800 TestOC[14492:913025] 2---<NSThread: 0x6000018c4300>{number = 1, name = main}
2020-07-13 13:36:58.414204+0800 TestOC[14492:913131] 3---<NSThread: 0x6000018a16c0>{number = 6, name = (null)}
2020-07-13 13:37:00.415765+0800 TestOC[14492:913025] 4---<NSThread: 0x6000018c4300>{number = 1, name = main}

小结:

我们可以发现:

嵌套队列之其他嵌套

死锁嵌套:串行队列嵌套串行队列

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{    // 异步执行 + 串行队列
        sleep(2);                                       // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        dispatch_sync(queue, ^{  // 同步执行 + 当前串行队列
            // 追加任务 1
            sleep(2);                                       // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });
    NSLog(@"---end");

输出结果:
2020-07-13 15:54:24.064504+0800 TestOC[15548:987245] currentThread---<NSThread: 0x600001edc9c0>{number = 1, name = main}
2020-07-13 15:54:24.064707+0800 TestOC[15548:987245] ---begin
2020-07-13 15:54:24.064875+0800 TestOC[15548:987245] ---end
2020-07-13 15:54:26.068574+0800 TestOC[15548:987599] 1---<NSThread: 0x600001ea1180>{number = 7, name = (null)}
(lldb)

小结:

我们可以发现:

嵌套队列的种类实在是太多了,而且在我代码生涯中,除了使用异步并发队列里面嵌套异步主队列这种外,剩下的都有用到,就不具体分析了,就在下面简单分析下同一个队列互相嵌套问题,如果对多队列互相嵌套或者多层嵌套有兴趣,可以自己去实验(话说根本用不到)
同一个队列互相嵌套组合的区别

区别 异步并发嵌套同一个并发队列 同步并发嵌套同一个并发队列 异步串行嵌套同一个串行队列 同步串行嵌套同一个串行队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行 死锁卡住不执行
异步(async) 有开启新线程,并发执行任务 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 有开启新线程(1条),串行执行任务
3.8、 GCD 的其他方法
3.8.1、GCD 栅栏方法:dispatch_barrier_async

有时候我们需要异步执行多组操作,比喻我以前的一个地铁类项目,需要先根据线路获取站点,在根据站点去获取对应数据,这样我们就一个将根据线路获取站点的操作分成一个组,等待这组所有请求都完成后再去执行根据站点去获取对应数据的请求。此时,我们将需要一个相当如栅栏的方法,将两组异步操作分割起来,执行完第一组的全部操作后再去执行第二组,当然,一个操作组里面可以包含多个任务,这就需要用到dispatch_barrier_async 方法在两个操作组间形成栅栏。
dispatch_barrier_async 方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将 dispatch_barrier_async 方法中追加的任务执行完毕之后,然后再追加任务到该异步队列并开始执行。先执行第一组任务,然后执行栅里面的任务,最后执行第二组任务,具体如下图所示:


栅栏.png
   //    栅栏方法 dispatch_barrier_async
   dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
   dispatch_async(queue, ^{
       // 追加任务 1
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
   });
   dispatch_async(queue, ^{
       // 追加任务 2
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
   });
   
   dispatch_barrier_async(queue, ^{
       // 追加任务 barrier1
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"barrier1---%@",[NSThread currentThread]);// 打印当前线程
   });
   
   dispatch_async(queue, ^{
       // 追加任务 3
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
   });
   dispatch_async(queue, ^{
       // 追加任务 4
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
   });

输出结果:
2020-07-13 17:05:20.408922+0800 TestOC[15836:1028107] 2---<NSThread: 0x60000116b580>{number = 3, name = (null)}
2020-07-13 17:05:20.408935+0800 TestOC[15836:1028147] 1---<NSThread: 0x60000110ec40>{number = 5, name = (null)}
2020-07-13 17:05:22.413781+0800 TestOC[15836:1028147] barrier1---<NSThread: 0x60000110ec40>{number = 5, name = (null)}
2020-07-13 17:05:24.417003+0800 TestOC[15836:1028147] 3---<NSThread: 0x60000110ec40>{number = 5, name = (null)}
2020-07-13 17:05:24.417000+0800 TestOC[15836:1028107] 4---<NSThread: 0x60000116b580>{number = 3, name = (null)}

小结:

我们可以发现:

3.8.2、GCD 延时执行方法:dispatch_after

在开发中,我们有时会遇见这样的需求,在2秒后执行某个任务,这是就可以用GCD的dispatch_after方法来实现

NSLog(@"---begin");
   //    延时执行方法 dispatch_after
   //    NSEC_PER_SEC表示秒,可以根据需求换成毫秒等
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       // 2.0 秒后异步追加任务代码到主队列,并开始执行
       NSLog(@"---after");  // 打印当前线程
   });
   NSLog(@"---end");

输出结果:
2020-07-13 17:14:40.454448+0800 TestOC[16025:1035485] ---begin
2020-07-13 17:14:40.454550+0800 TestOC[16025:1035485] ---end
2020-07-13 17:14:42.454623+0800 TestOC[16025:1035485] ---after

小结:

我们可以发现:

3.8.3、 GCD 一次性代码(只执行一次):dispatch_once

有时为了优化程序,有的代码在整个程序运行过程中只需执行一次时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。最常用的地方就是创建单例。如:现在需要创建一个单例AppConfig

static AppConfig *appConfig = nil;

//单例
+ (AppConfig *)shareInstance {
   
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       //不管调用shareInstance多少次,初始化appConfig只会执行一次
       appConfig = [[AppConfig alloc] init];
   });
   return appConfig;
}
3.8.4、 GCD 快速迭代方法:dispatch_apply

通常我们会用 for 循环遍历,但是如果里面操作比较耗时,那么加起来等待的时间就会比较久,此时就可以使用GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束后在继续执行后续任务。
如果在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行,这样就体现不出快速迭代的意义了。
但是在并发队列中使用 dispatch_apply,那么就会并发去处理耗时操作, 这样执行起来就会比for 循环节省时间了。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。

NSLog(@"---begin");
   //1.创建NSArray类对象
   NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
   //2.创建一个全局队列
   dispatch_queue_t queue = dispatch_queue_create("sky.testQueue", DISPATCH_QUEUE_CONCURRENT);
   //3.通过dispatch_apply函数对NSArray中的全部元素进行处理,并等待处理完成,
   dispatch_apply([array count], queue, ^(size_t index) {
       sleep(2);
       NSLog(@"%zd: %@", index, [array objectAtIndex:index]);
   });
   NSLog(@"---end");

输出结果:
2020-07-13 17:44:18.256883+0800 TestOC[1323:216847] ---begin
2020-07-13 17:44:20.258034+0800 TestOC[1323:217073] 0: a
2020-07-13 17:44:20.258037+0800 TestOC[1323:217067] 1: b
2020-07-13 17:44:20.258089+0800 TestOC[1323:216847] 2: c
2020-07-13 17:44:20.258094+0800 TestOC[1323:217095] 5: f
2020-07-13 17:44:20.258228+0800 TestOC[1323:217094] 3: d
2020-07-13 17:44:20.258232+0800 TestOC[1323:217093] 4: e
2020-07-13 17:44:22.259244+0800 TestOC[1323:217067] 6: g
2020-07-13 17:44:22.259244+0800 TestOC[1323:216847] 9: j
2020-07-13 17:44:22.259256+0800 TestOC[1323:217095] 8: i
2020-07-13 17:44:22.259244+0800 TestOC[1323:217073] 7: h
2020-07-13 17:44:22.259667+0800 TestOC[1323:216847] ---end

小结:

我们可以发现:

3.8.5、GCD 队列组:dispatch_group
a、dispatch_group_wait

有时我们会遇见这样的需求,登陆的时候获取从多了接口基础数据,当所有的接口数据都获取完后,再跳转到登陆界面,这时候我们可以用到 GCD 的队列组。

   NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
   NSLog(@"group---begin");
   
   dispatch_group_t group =  dispatch_group_create();
   
   dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 追加任务 1
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
   });
   
   dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 追加任务 2
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
   });
   
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
       
       NSLog(@"group---end");
   });
   NSLog(@"group---end");

输出结果:
2020-07-13 19:52:11.985055+0800 TestOC[17048:1121766] currentThread---<NSThread: 0x600001798780>{number = 1, name = main}
2020-07-13 19:52:11.985256+0800 TestOC[17048:1121766] group---begin
2020-07-13 19:52:11.985446+0800 TestOC[17048:1121766] group---end
2020-07-13 19:52:13.986024+0800 TestOC[17048:1122478] 1---<NSThread: 0x6000017e1240>{number = 6, name = (null)}
2020-07-13 19:52:13.986023+0800 TestOC[17048:1122378] 2---<NSThread: 0x60000178c380>{number = 4, name = (null)}
2020-07-13 19:52:15.986499+0800 TestOC[17048:1121766] 3---<NSThread: 0x600001798780>{number = 1, name = main}
2020-07-13 19:52:15.986838+0800 TestOC[17048:1121766] group---end

小结:

我们可以发现:

b、dispatch_group_wait

会暂停当前线程,等指定的group中任务全部完成后,才会继续执行

NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
   NSLog(@"group---begin");
   dispatch_group_t group =  dispatch_group_create();
   dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 追加任务 1
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
   });
   
   dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 追加任务 2
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
   });
   
   // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
   dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
   
   NSLog(@"group---end");

输出结果:
2020-07-14 16:09:34.610211+0800 TestOC[22992:1463372] currentThread---<NSThread: 0x6000009bc700>{number = 1, name = main}
2020-07-14 16:09:34.610327+0800 TestOC[22992:1463372] group---begin
2020-07-14 16:09:36.615548+0800 TestOC[22992:1463487] 1---<NSThread: 0x6000009c4280>{number = 8, name = (null)}
2020-07-14 16:09:36.615548+0800 TestOC[22992:1463486] 2---<NSThread: 0x6000009d5900>{number = 7, name = (null)}
2020-07-14 16:09:36.615707+0800 TestOC[22992:1463372] group---end

小结:

我们可以发现:

c、dispatch_group_enter、dispatch_group_leave
   NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
   NSLog(@"group---begin");
   //   使用dispatch_group_enter、dispatch_group_leave 组合
   dispatch_group_t group =  dispatch_group_create();
   
   dispatch_group_enter(group);
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 追加任务 1
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
       dispatch_group_leave(group);
   });
   
   dispatch_group_enter(group);
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 追加任务 2
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
       dispatch_group_leave(group);
   });
   
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
       sleep(2);                                       // 模拟耗时操作
       NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
   });
   NSLog(@"group---end");

输出结果:
2020-07-14 16:22:55.973348+0800 TestOC[23194:1474590] currentThread---<NSThread: 0x600000270ac0>{number = 1, name = main}
2020-07-14 16:22:55.973507+0800 TestOC[23194:1474590] group---begin
2020-07-14 16:22:55.973626+0800 TestOC[23194:1474590] group---end
2020-07-14 16:22:57.977584+0800 TestOC[23194:1474710] 1---<NSThread: 0x60000023a540>{number = 3, name = (null)}
2020-07-14 16:22:57.977614+0800 TestOC[23194:1474711] 2---<NSThread: 0x6000002269c0>{number = 7, name = (null)}
2020-07-14 16:22:59.978804+0800 TestOC[23194:1474590] 3---<NSThread: 0x600000270ac0>{number = 1, name = main}

小结:

我们可以发现:

3.8.6、 GCD 信号量:dispatch_semaphore
a、控制同时最多并发量

有时候我们会遇到这种需求,如视频下载,同时最多可以下载5个视频。这是我们就可以使用dispatch_semaphore,让加在队列中的任务一次最多同时执行5个,剩下的在后面等待,等前面的执行的任务有执行完毕的,再将等待的任务追加到执行。
在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,直接通过。
Dispatch Semaphore 提供了三个方法:
dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_signal:发送一个信号,让信号总量加 1
dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

   NSLog(@"group---begin");
   //   使用dispatch_group_enter、dispatch_group_leave 组合
   dispatch_group_t group =  dispatch_group_create();
   dispatch_semaphore_t fileSemaphore = dispatch_semaphore_create(5);     //同时处理5个
   for (NSInteger i = 0; i < 10; i ++) {
       dispatch_semaphore_wait(fileSemaphore, DISPATCH_TIME_FOREVER);
       dispatch_group_enter(group);
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           
           sleep(2);            // 模拟耗时操作
           NSLog(@"%ld---%@",i,[NSThread currentThread]);
           dispatch_group_leave(group);
           dispatch_semaphore_signal(fileSemaphore);
       });
   }
   
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // 等前面的异步任务都执行完毕后,回到主线程执行下边任务
       NSLog(@"group---end");
   });

输出结果:
2020-07-14 18:18:34.603300+0800 TestOC[24509:1548769] group---begin
2020-07-14 18:18:36.604181+0800 TestOC[24509:1548875] 3---<NSThread: 0x6000032f8200>{number = 7, name = (null)}
2020-07-14 18:18:36.604199+0800 TestOC[24509:1548881] 1---<NSThread: 0x6000032c45c0>{number = 6, name = (null)}
2020-07-14 18:18:36.604200+0800 TestOC[24509:1548876] 4---<NSThread: 0x6000032e8380>{number = 4, name = (null)}
2020-07-14 18:18:36.604213+0800 TestOC[24509:1548882] 2---<NSThread: 0x600003297b40>{number = 3, name = (null)}
2020-07-14 18:18:36.604215+0800 TestOC[24509:1548880] 0---<NSThread: 0x6000032e75c0>{number = 5, name = (null)}
2020-07-14 18:18:38.608774+0800 TestOC[24509:1548876] 6---<NSThread: 0x6000032e8380>{number = 4, name = (null)}
2020-07-14 18:18:38.608774+0800 TestOC[24509:1548881] 7---<NSThread: 0x6000032c45c0>{number = 6, name = (null)}
2020-07-14 18:18:38.608774+0800 TestOC[24509:1548880] 8---<NSThread: 0x6000032e75c0>{number = 5, name = (null)}
2020-07-14 18:18:38.608797+0800 TestOC[24509:1548875] 9---<NSThread: 0x6000032f8200>{number = 7, name = (null)}
2020-07-14 18:18:38.608781+0800 TestOC[24509:1548882] 5---<NSThread: 0x600003297b40>{number = 3, name = (null)}
2020-07-14 18:18:38.609333+0800 TestOC[24509:1548769] group---end

小结:

我们可以发现:

b、线程安全

Dispatch Semaphore实现线程安全就是将异步并发在某一时刻变成同步,也就是为线程加锁。
比喻现在有100张火车票,现在有100个人同时购买,如果不实现线程安全直接实现,代码如下

NSLog(@"group---begin");
   dispatch_group_t group =  dispatch_group_create();
   _dataInt = 100;
   for (NSInteger i = 0; i < 100; i ++) {
       dispatch_group_enter(group);
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           
           sleep(2);            // 模拟耗时操作
           self.dataInt --;
           NSLog(@"%ld",self.dataInt);
           dispatch_group_leave(group);
       });
   }
   
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // 票买完了,回到主线程查看是否还有余票
       NSLog(@"打印剩余票数---%ld",self.dataInt);
       NSLog(@"group---end");
   });

dispatch_group_notify里面是不是应该打印0,但是在时间测试中,并不是

输出结果:
2020-07-15 10:22:26.427131+0800 TestOC[26758:1684612] group---begin
2020-07-15 10:22:28.429319+0800 TestOC[26758:1685009] 99
2020-07-15 10:22:28.429341+0800 TestOC[26758:1685391] 99
2020-07-15 10:22:28.433020+0800 TestOC[26758:1685390] 97
2020-07-15 10:22:28.433039+0800 TestOC[26758:1685392] 98
2020-07-15 10:22:28.433073+0800 TestOC[26758:1685426] 96
2020-07-15 10:22:28.433096+0800 TestOC[26758:1685393] 98
...
2020-07-15 10:22:30.454582+0800 TestOC[26758:1685476] 14
2020-07-15 10:22:30.454581+0800 TestOC[26758:1685477] 13
2020-07-15 10:22:30.454589+0800 TestOC[26758:1685481] 11
2020-07-15 10:22:30.454538+0800 TestOC[26758:1685443] 21
2020-07-15 10:22:30.454554+0800 TestOC[26758:1685446] 20
2020-07-15 10:22:30.465575+0800 TestOC[26758:1684612] 打印剩余票数---11
2020-07-15 10:22:30.465780+0800 TestOC[26758:1684612] group---end

小结:

我们可以发现:

现在我们用dispatch_semaphore来实现一下线程安全
NSLog(@"group---begin");
   dispatch_group_t group =  dispatch_group_create();
   _dataInt = 100;
   dispatch_semaphore_t fileSemaphore = dispatch_semaphore_create(1);
   for (NSInteger i = 0; i < 100; i ++) {
       dispatch_group_enter(group);
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           
           sleep(2);            // 模拟耗时操作
           dispatch_semaphore_wait(fileSemaphore, DISPATCH_TIME_FOREVER);
           self.dataInt --;
           NSLog(@"%ld",self.dataInt);
           dispatch_semaphore_signal(fileSemaphore);
           dispatch_group_leave(group);
       });
   }
   
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // 票买完了,回到主线程查看是否还有余票
       NSLog(@"打印剩余票数---%ld",self.dataInt);
       NSLog(@"group---end");
   });

输出结果:
2020-07-15 10:35:45.916742+0800 TestOC[26880:1694893] group---begin
2020-07-15 10:35:47.921142+0800 TestOC[26880:1695014] 99
2020-07-15 10:35:47.921701+0800 TestOC[26880:1695058] 98
2020-07-15 10:35:47.922048+0800 TestOC[26880:1695003] 97
2020-07-15 10:35:47.922335+0800 TestOC[26880:1695012] 96
2020-07-15 10:35:47.922648+0800 TestOC[26880:1695059] 95
...
2020-07-15 10:35:49.950237+0800 TestOC[26880:1695100] 4
2020-07-15 10:35:49.950387+0800 TestOC[26880:1695102] 3
2020-07-15 10:35:49.950507+0800 TestOC[26880:1695103] 2
2020-07-15 10:35:49.950770+0800 TestOC[26880:1695104] 1
2020-07-15 10:35:49.951149+0800 TestOC[26880:1695101] 0
2020-07-15 10:35:49.951582+0800 TestOC[26880:1694893] 打印剩余票数---0
2020-07-15 10:35:49.951890+0800 TestOC[26880:1694893] group---end

小结:

我们可以发现:

第一次写这种长篇分享,如果里面有不合理的地方欢迎在评论区提出。
上一篇下一篇

猜你喜欢

热点阅读