ios 多线程-GCD

2020-02-25  本文已影响0人  瞬间完善

多线程原理这篇文章中,我们讲述了关于多线程的一些基础点和需要注意的事项。就目前开发情况来看我们大多数情况都是用GCD就能解决了,今天我们就来探讨一下GCD的用法。

GCD

GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。三种开发中GCD抽象层次最高,当然是用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的。对于熟悉C#异步调用的朋友对于GCD学习起来应该很快,因为它与C#中的异步调用基本是一样的。这种机制相比较于前面两种多线程开发方式最显著的优点就是它对于多核运算更加有效。

名词解释

任务:执行什么操作
队列:用来存放任务,将需要执行的任务添加到队列中,队列会遵循FIFO原则(先进先出、后进后出),将队列中的任务取出,放到对应的线程中执行

同步:不创建新的线程,只在当前线程中执行任务
异步:创建多条线程执行任务

串行:同一时间每次只能执行一个任务,当前任务未完成下一个任务只能在队列中等候
并发:同一时间可以执行多个任务

死锁:两个或多个任务互相等待形成死循环阻塞了线程,甚至导致应用无响应

关系

串行 并行 主队列
同步 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁
异步 开启新线程,串行执行任务 开启新线程,并发执行任务 没有开启新线程,串行执行任务

1、队列

队列的特点:遵循先进先出FIFO(First In First Out)的原则,排在前面的任务最先执行。
创建队列使用dispatch_queue_create,有两个参数,第一个参数是队列名称,第二个参数是队列类型,通常创建串行队列类型传NULL,我们也可以使用dispatch_queue_attr_t定义好的常量创建同步/并发队列

/**
dispatch_queue_attr_t
DISPATCH_QUEUE_SERIAL: 同步队列
DISPATCH_QUEUE_CONCURRENT:并发队列
*/
dispatch_queue_t queue = dispatch_queue_create ( const char *label, dispatch_queue_attr_t attr );
1、串行队列
dispatch_queue_t queue = dispatch_queue_create("com.yx.customQueue", NULL);
diapatch_queue_t queue = dispatch_get_main_queue();
2、并发队列

并发队列同样是使用dispatch_queue_create()方法创建,只是最后一个参数指定为DISPATCH_QUEUE_CONCURRENT进行创建,但是在实际开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列(当然如果有多个并发队列可以使用前者创建)。

/**
队列优先级
DISPATCH_QUEUE_PRIORITY_HIGH 2               // 高
DISPATCH_QUEUE_PRIORITY_DEFAULT 0            // 默认(中)
DISPATCH_QUEUE_PRIORITY_LOW (-2)             // 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
// dispatch_queue_t queue = dispatch_queue_create("com.yx.customQueue", DISPATCH_QUEUE_CONCURRENT);
举例
1、同步/异步串行队列
// ✅ 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("yx.com", NULL);
    NSLog(@"begin");
// ✅ 异步串行队列
    dispatch_async(queue, ^{
        NSLog(@"异步线程1 - 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"队列外1 - 当前线程:%@",[NSThread currentThread]);

    dispatch_async(queue, ^{
        NSLog(@"异步线程2 - 当前线程:%@",[NSThread currentThread]);
    });
    
    NSLog(@"队列外2 - 当前线程:%@",[NSThread currentThread]);
// ✅ 同步串行队列
    dispatch_sync(queue, ^{
        NSLog(@"同步线程1 - 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"队列外3 - 当前线程:%@",[NSThread currentThread]);
// ✅ 异步串行队列
    dispatch_async(queue, ^{
        NSLog(@"异步线程3 - 当前线程:%@",[NSThread currentThread]);
    });

输出:

2020-02-22 17:06:06.725804+0800 004---线程通讯[17706:205885] begin
2020-02-22 17:06:06.726097+0800 004---线程通讯[17706:205885] 队列外1 - 当前线程:<NSThread: 0x6000034f6900>{number = 1, name = main}
2020-02-22 17:06:06.726097+0800 004---线程通讯[17706:206188] 异步线程1 - 当前线程:<NSThread: 0x600003492c80>{number = 3, name = (null)}
2020-02-22 17:06:06.726239+0800 004---线程通讯[17706:206188] 异步线程2 - 当前线程:<NSThread: 0x600003492c80>{number = 3, name = (null)}
2020-02-22 17:06:06.726242+0800 004---线程通讯[17706:205885] 队列外2 - 当前线程:<NSThread: 0x6000034f6900>{number = 1, name = main}
2020-02-22 17:06:06.726358+0800 004---线程通讯[17706:205885] 同步线程1 - 当前线程:<NSThread: 0x6000034f6900>{number = 1, name = main}
2020-02-22 17:06:06.726471+0800 004---线程通讯[17706:205885] 队列外3 - 当前线程:<NSThread: 0x6000034f6900>{number = 1, name = main}
2020-02-22 17:06:06.726621+0800 004---线程通讯[17706:206188] 异步线程3 - 当前线程:<NSThread: 0x600003492c80>{number = 3, name = (null)}
小结
2、同步/异步并发队列
 // ✅ 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 // ✅ 手动创建并发队列
 // dispatch_queue_t queue = dispatch_queue_create("yx.com", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"Begin");
 // ✅ 异步并发队列
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"异步任务1 - 当前线程 - %@",[NSThread currentThread]);
    });
    
    NSLog(@"队列1 - 当前线程 - %@",[NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"异步任务2 - 当前线程 - %@",[NSThread currentThread]);
    });
    
     NSLog(@"队列2 - 当前线程 - %@",[NSThread currentThread]);
    
 // ✅ 同步并发队列
    dispatch_sync(queue, ^{
        NSLog(@"同步任务1 - 当前线程 - %@",[NSThread currentThread]);
    });
    
     NSLog(@"队列3 - 当前线程 - %@",[NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"异步任务3 - 当前线程 - %@",[NSThread currentThread]);
    });
    
    NSLog(@"End");

输出:

2020-02-25 10:50:40.431234+0800 003---GCD应用[9132:83160] Begin
2020-02-25 10:50:40.431455+0800 003---GCD应用[9132:83160] 队列1 - 当前线程 - <NSThread: 0x6000004e9000>{number = 1, name = main}
2020-02-25 10:50:40.431632+0800 003---GCD应用[9132:83160] 队列2 - 当前线程 - <NSThread: 0x6000004e9000>{number = 1, name = main}
2020-02-25 10:50:40.431762+0800 003---GCD应用[9132:83160] 同步任务1 - 当前线程 - <NSThread: 0x6000004e9000>{number = 1, name = main}
2020-02-25 10:50:40.431869+0800 003---GCD应用[9132:83160] 队列3 - 当前线程 - <NSThread: 0x6000004e9000>{number = 1, name = main}
2020-02-25 10:50:40.431980+0800 003---GCD应用[9132:83160] End
2020-02-25 10:50:43.431661+0800 003---GCD应用[9132:83442] 异步任务1 - 当前线程 - <NSThread: 0x600000468200>{number = 3, name = (null)}
2020-02-25 10:50:43.432114+0800 003---GCD应用[9132:83449] 异步任务2 - 当前线程 - <NSThread: 0x600000468580>{number = 4, name = (null)}
2020-02-25 10:50:43.432469+0800 003---GCD应用[9132:84274] 异步任务3 - 当前线程 - <NSThread: 0x600000468600>{number = 5, name = (null)}
小结:

2、线程间的通讯

在ios开发过程中,我们一般在主线程里面进行UI的刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而且我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

// ✅ 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // ✅ 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // ✅ 创建异步任务
    dispatch_async(queue, ^{
        NSLog(@"执行任务 - 当前线程:%@",[NSThread currentThread]);
        sleep(3);
                
        dispatch_async(mainQueue, ^{
            NSLog(@"回来了 - 当前线程:%@",[NSThread currentThread]);
        });
        
    });

输出:

2020-02-25 15:15:13.007441+0800 003---GCD应用[25369:271831] 执行任务 - 当前线程:<NSThread: 0x600001dbfc00>{number = 3, name = (null)}
2020-02-25 15:15:16.012709+0800 003---GCD应用[25369:271762] 回来了 - 当前线程:<NSThread: 0x600001dc6900>{number = 1, name = main}

3、栅栏方法

1、dispatch_barrier_async

    dispatch_queue_t queue = dispatch_queue_create("yx.com", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"Begin");
    
    NSLog(@"队列任务1 - 当前线程:%@",[NSThread currentThread]);
    // ✅ 创建异步栅栏
    dispatch_barrier_async(queue, ^{
        sleep(3);
        NSLog(@"栅栏任务1- 当前线程:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"异步任务1- 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"队列任务2 - 当前线程:%@",[NSThread currentThread]);
    
    dispatch_sync(queue, ^{
        
        NSLog(@"同步任务1- 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"End");

输出:

2020-02-25 14:57:14.000492+0800 003---GCD应用[24130:254960] Begin
2020-02-25 14:57:14.001127+0800 003---GCD应用[24130:254960] 队列任务1 - 当前线程:<NSThread: 0x6000027be1c0>{number = 1, name = main}
2020-02-25 14:57:14.001319+0800 003---GCD应用[24130:254960] 队列任务2 - 当前线程:<NSThread: 0x6000027be1c0>{number = 1, name = main}
2020-02-25 14:57:17.003433+0800 003---GCD应用[24130:255085] 栅栏任务1- 当前线程:<NSThread: 0x6000027d3a00>{number = 3, name = (null)}
2020-02-25 14:57:17.003697+0800 003---GCD应用[24130:254960] 同步任务1- 当前线程:<NSThread: 0x6000027be1c0>{number = 1, name = main}
2020-02-25 14:57:17.003747+0800 003---GCD应用[24130:255708] 异步任务1- 当前线程:<NSThread: 0x6000027e4540>{number = 4, name = (null)}
2020-02-25 14:57:17.003812+0800 003---GCD应用[24130:254960] End
2、dispatch_barrier_sync
   dispatch_queue_t queue = dispatch_queue_create("yx.com", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"Begin");
    // ✅ 创建同步栅栏
    dispatch_barrier_sync(queue, ^{
        sleep(3);
        NSLog(@"栅栏任务1- 当前线程:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"异步任务1- 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"队列任务1 - 当前线程:%@",[NSThread currentThread]);
    
    dispatch_sync(queue, ^{
        
        NSLog(@"同步任务1- 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"End");

输出:

2020-02-25 14:50:12.043837+0800 003---GCD应用[23685:249681] Begin
2020-02-25 14:50:15.044208+0800 003---GCD应用[23685:249681] 栅栏任务1- 当前线程:<NSThread: 0x600000ff0100>{number = 1, name = main}
2020-02-25 14:50:15.044446+0800 003---GCD应用[23685:249681] 队列任务1 - 当前线程:<NSThread: 0x600000ff0100>{number = 1, name = main}
2020-02-25 14:50:15.044457+0800 003---GCD应用[23685:249758] 异步任务1- 当前线程:<NSThread: 0x600000f8cf00>{number = 3, name = (null)}
2020-02-25 14:50:15.044594+0800 003---GCD应用[23685:249681] 同步任务1- 当前线程:<NSThread: 0x600000ff0100>{number = 1, name = main}
2020-02-25 14:50:15.044688+0800 003---GCD应用[23685:249681] End

4、信号量

我们带着问题来进行探究:
假设我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,我们怎么做?这个时候我们这里就可以用信号量控制一下最大开辟线程数。

定义

信号量就是一种可用来控制访问资源的数量的标识,设定一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们制定的信号量数量来执行多个线程。

函数介绍

GCD信号量主要有3个函数:

举例:

限制线程最大并发数

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // ✅ 信号量 -- gcd控制并发数
    // ✅ 同步
    // ✅ 总结:由于设定的信号值为2,先执行2个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);

    // ✅ 任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1");
        sleep(3);
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(semaphore);
    });

    // ✅ 任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务2");
        sleep(1);
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(semaphore);
    });

    // ✅ 任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务3");
        sleep(1);
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(semaphore);
    });

输出:

2020-02-25 16:26:34.830331+0800 003---GCD应用[30462:334167] 执行任务1
2020-02-25 16:26:34.830355+0800 003---GCD应用[30462:334166] 执行任务3
2020-02-25 16:26:35.834183+0800 003---GCD应用[30462:334166] 任务3完成
2020-02-25 16:26:35.834384+0800 003---GCD应用[30462:334168] 执行任务2
2020-02-25 16:26:36.837841+0800 003---GCD应用[30462:334168] 任务2完成
2020-02-25 16:26:37.834082+0800 003---GCD应用[30462:334167] 任务1完成
小结:

由于设定的信号值为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。

5、调度组

可以将一组block提交到调度组(dispatch_group)中,执行逐个串行回调。

函数介绍
示例:
1、dispatch_group_async
// ✅ 创建调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // ✅ 创建组内任务
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务y1完成");
        sleep(3);
    });
    dispatch_group_async(group, queue, ^{

        NSLog(@"任务2完成");
    });
    
    // ✅ 设置等待
    dispatch_async(queue, ^{
     // ✅ DISPATCH_TIME_NOW 这里是超过设置的等待时间就向下执行。
     // ✅ DISPATCH_TIME_FOREVER 这里是等待组内任务执行完毕后继续执行,不管等待时间。
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 ));
        NSLog(@"结束");
    });
    
    // ✅ 创建组内任务完成
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
         NSLog(@"全部完成");
    });

输出:

2020-02-25 17:45:09.020782+0800 003---GCD应用[35685:404362] 任务2完成
2020-02-25 17:45:09.020784+0800 003---GCD应用[35685:404363] 结束
2020-02-25 17:45:12.025171+0800 003---GCD应用[35685:403742] 任务1完成
2020-02-25 17:45:12.025554+0800 003---GCD应用[35685:403650] 全部完成
2、dispatch_group_enter(group)dispatch_group_leave(group)
 // ✅ 创建调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // ✅ 将异步任务添加进组
    dispatch_group_enter(group);
    // ✅ 创建异步任务
    dispatch_async(queue, ^{
        NSLog(@"任务1完成");
        // ✅ 异步任务完成出组
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"任务2完成");
        dispatch_group_leave(group);
    });
    
    // ✅ 设置等待
    dispatch_async(queue, ^{
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 ));
        NSLog(@"结束");
    });
    
    // ✅ 创建组内任务完成
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部完成");
    });

输出:

2020-02-25 17:49:30.070295+0800 003---GCD应用[36015:408141] 任务1完成
2020-02-25 17:49:30.070295+0800 003---GCD应用[36015:408138] 任务2完成
2020-02-25 17:49:30.070295+0800 003---GCD应用[36015:408146] 结束
2020-02-25 17:49:30.103424+0800 003---GCD应用[36015:408021] 全部完成

6、延迟调用

我们经常会遇到这样的需求:在指定时间(例如 3 秒)之后执行某个任务。可以用 GCDdispatch_after 方法来实现。

注意dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after方法是很有效的。

 // ✅ NSEC_PER_SEC : 1000000000ull 纳秒每秒 0.0000001 可以这么做参数
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
    // ✅ 串行队列来测试 延迟的方法是不是异步的!
    dispatch_queue_t queue = dispatch_queue_create("yx.com", DISPATCH_QUEUE_SERIAL);
    dispatch_after(time, queue, ^{
        NSLog(@"延迟打印");
    });
    NSLog(@"打印完了?");

输出:

2020-02-25 17:55:03.089045+0800 003---GCD应用[36371:412715] 打印完了?
2020-02-25 17:55:04.184112+0800 003---GCD应用[36371:413051] 延迟打印
小结:

由输出可以看出1 秒后异步追加任务代码到主队列,并开始执行。

以上是个人理解关于GCD的一些简单用法!闲来无事,做做记录!

上一篇下一篇

猜你喜欢

热点阅读