iOS多线程之GCD<二>

前面说了iOS使用多线程共有四种方式,Pthread和NSThread也已经讲过了,这一次主要学习Grand Central Dispatch,也就是GCD。
GCD的使用不需要我们自己管理生命周期,也就是不用我们再做创建线程,启动线程等等这些繁琐的操作,我们只需要三个步骤:
1、创建或者获取队列
2、创建任务
3、把任务加进队列
所以,我们要学会GCD就要搞懂两个概念:队列和任务。

队列 百度百科这么定义的:队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。我们这里的队列就是存放任务的线性表,有两种形式:串行队列并行队列
串行队列 队列中的任务一个接一个的执行,主队列也是一种特殊的串行队列,主队列是程序启动就已经创建好了的,所以我们如果要使用只需要获取就可以了,主队列中的任务都是在主线程中执行的。
并行队列 并行队列的任务是一起执行的(前提是任务能够开辟新线程),全局队列就是一种特殊的并发队列,也是系统帮我们创建好的,我们只需获取就可以使用。

任务 其实就是你要执行的代码,任务也是有两种形式:同步任务异步任务
同步任务 不会开辟新线程,在当前线程执行。
异步任务 开辟新线程,在新线程中执行。

从前面学习到的知识我们知道,队列和任务是要配合使用的,所以,两种队列、两种任务一共就是四种组合模式。
串行同步 当前线程中一个接一个的执行,如果是在主队列中执行同步任务,会造成死锁,不是我们这章的重点,需要的请看我的理解
串行异步 会开辟一条新线程,然后一个接一个的执行。
并行同步 不会开辟新线程,一个接一个执行。
并行异步 会开辟新线程,同时执行。
总结,开辟不开辟新线程由任务决定,执行顺序共同决定,要同时执行多个任务,只能并行异步。

说了很多了,是时候来点实际的
串行同步

- (void)serialSync {
    dispatch_queue_t dispatchQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);//创建一个串行队列
    dispatch_sync(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:1];//线程睡眠1秒
        NSLog(@"dispatch-serail-1----%@", [NSThread currentThread]);
    }); //添加一个同步任务
    dispatch_sync(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch-serail-2---%@", [NSThread currentThread]);
    });
    dispatch_sync(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:3];
        NSLog(@"dispatch-serail-3---%@", [NSThread currentThread]);
    });
}

下面看一下打印结果

151503551729_.pic_hd.jpg

在主线程中执行,并且时间间隔分别是2秒和3秒,看来我们前面的推断没有错,串行同步不开辟新县城并且顺序执行。

串行异步

- (void)serialAsync {
    NSLog(@"dispatch-serail-0----%@", [NSThread currentThread]);
    dispatch_queue_t dispatchQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);//还是串行队列
    dispatch_async(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch-serail-1----%@", [NSThread currentThread]);
    });
    dispatch_async(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch-serail-2---%@", [NSThread currentThread]);
    });
    dispatch_async(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:3];
        NSLog(@"dispatch-serail-3---%@", [NSThread currentThread]);
    });
}

看结果


161503553334_.pic_hd.jpg

串行异步开辟一条线程顺序执行。

并行同步

- (void)concurrentSync {
    dispatch_queue_t dispatchQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);//创建并行队列DISPATCH_QUEUE_CONCURRENT
    dispatch_sync(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch-serail-1----%@", [NSThread currentThread]);
    });
    dispatch_sync(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch-serail-2---%@", [NSThread currentThread]);
    });
    dispatch_sync(dispatchQueue, ^(){
        [NSThread sleepForTimeInterval:3];
        NSLog(@"dispatch-serail-3---%@", [NSThread currentThread]);
    });
}

结果


171503554226_.pic_hd.jpg

在主线程中顺序执行

并行异步

- (void)concurrentAsync {
    NSLog(@"dispatch-serail-0----%@", [NSThread currentThread]);
    dispatch_queue_t dispatchQueue2 = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(dispatchQueue2, ^(){
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch-serail-1----%@", [NSThread currentThread]);
    });
    dispatch_async(dispatchQueue2, ^(){
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch-serail-2---%@", [NSThread currentThread]);
    });
    dispatch_async(dispatchQueue2, ^(){
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch-serail-3---%@", [NSThread currentThread]);
    });
}

看结果



看number表明它们都开辟了新线程,看时间是同时执行的,看黄框表明它们顺序是不定的。这就是真正意义上的异步执行。

基本用法我们搞明白了,让我们一起研究一下还有一些什么别的用法把。

- (void)apply {
    dispatch_queue_t dispatchQueue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(3, dispatchQueue, ^(size_t t){
        [NSThread sleepForTimeInterval:1];
        NSLog(@"tttt : %zu" , t);
    });
}
2017-08-24 14:21:32.690 thread[54949:4940796] tttt : 0
2017-08-24 14:21:32.690 thread[54949:4940842] tttt : 2
2017-08-24 14:21:32.690 thread[54949:4940860] tttt : 1

任务迭代执行,还是很有用的,比如说要对某个数组进行无序遍历,用任务迭代执行还是比较快的。

看一下延时方法

NSLog(@"after-0----%@", [NSThread currentThread]);
    dispatch_queue_t dispatchQueue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatchQueue, ^{
        // 延时两秒执行
        NSLog(@"affer--1---%@", [NSThread currentThread]);
    });

2017-08-24 14:45:20.062 thread[55200:4960926] after-0----<NSThread: 0x600000066e00>{number = 1, name = main}
2017-08-24 14:45:22.261 thread[55200:4960978] affer--1---<NSThread: 0x60000006d0c0>{number = 3, name = (null)}

延时两秒之后开辟新线程执行
栅栏任务

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"barrier--1---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"barrier--2---%@", [NSThread currentThread]);
    });
    
//    dispatch_barrier_async(queue, ^{
//        [NSThread sleepForTimeInterval:1];
//        NSLog(@"barrier--3---%@", [NSThread currentThread]);
//    });
    
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"barrier--4---%@", [NSThread currentThread]);
    });
}

先看一下在启用栅栏之前,这几个任务应该是同时执行的

2017-08-24 14:55:51.389 thread[55322:4971612] barrier--4---<NSThread: 0x608000266600>{number = 5, name = (null)}
2017-08-24 14:55:51.389 thread[55322:4971630] barrier--1---<NSThread: 0x600000268e00>{number = 4, name = (null)}
2017-08-24 14:55:51.389 thread[55322:4971614] barrier--2---<NSThread: 0x600000262c00>{number = 3, name = (null)}

在看一下启用栅栏之后的情况,1和2是同时执行,3是在1和2执行结束之后开始执行,4是在3结束之后执行

2017-08-24 16:59:53.325 thread[1007:39054] barrier--1---<NSThread: 0x600000269a40>{number = 3, name = (null)}
2017-08-24 16:59:53.325 thread[1007:39070] barrier--2---<NSThread: 0x60800007fb00>{number = 4, name = (null)}
2017-08-24 16:59:54.328 thread[1007:39070] barrier--3---<NSThread: 0x60800007fb00>{number = 4, name = (null)}
2017-08-24 16:59:55.333 thread[1007:39070] barrier--4---<NSThread: 0x60800007fb00>{number = 4, name = (null)}

只执行一次的任务,通常用于单例模式中创建单例,而且是线程安全的

- (void)viewDidLoad {
    [super viewDidLoad];
    [self once];
    [self once];
}

- (void)once {
    NSLog(@"-----once");
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"once---%@", [NSThread currentThread]);
    });
}

看结果是不是真的块方法只打印了一次

2017-08-24 17:10:28.742 thread[1043:48637] -----once
2017-08-24 17:10:28.743 thread[1043:48637] once---<NSThread: 0x60800007bac0>{number = 1, name = main}
2017-08-24 17:10:28.743 thread[1043:48637] -----once

在看一下分组任务

- (void)group {
    dispatch_group_t group =  dispatch_group_create();//创建一个分组
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{//任务放在group 组中
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group--1---%@", [NSThread currentThread]);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group--2---%@", [NSThread currentThread]);
    });
    //观察一个分组任务全部完成的通知,然后执行
    dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group--3---%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group--4---%@", [NSThread currentThread]);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"barrier--5---%@", [NSThread currentThread]);
    });
}
2017-08-24 17:25:15.583 thread[1144:62230] group--1---<NSThread: 0x608000261d00>{number = 3, name = (null)}
2017-08-24 17:25:15.583 thread[1144:62247] group--2---<NSThread: 0x6000002633c0>{number = 4, name = (null)}
2017-08-24 17:25:15.583 thread[1144:62248] group--5---<NSThread: 0x60000026a140>{number = 5, name = (null)}
2017-08-24 17:25:16.582 thread[1144:62233] group--4---<NSThread: 0x60000007f8c0>{number = 6, name = (null)}
2017-08-24 17:25:17.588 thread[1144:62233] group--3---<NSThread: 0x60000007f8c0>{number = 6, name = (null)}

从4的执行时间我们能看出来通知任务会等组中的任务全部完成才会执行,不管任务是在它之前还是在它之后添加,而且从5的执行结果看不会对别的任务产生影响。

GCD中信号量的使用

信号量(semaphore)是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap) , signal(semap) ; 来进行访问;
操作也被成为PV原语(P来源于Dutch proberen"测试",V来源于Dutch verhogen"增加"),而普通整型变量则可以在任何语句块中被访问;

在GCD中有semaphore的操作:
dispatch_semaphore_create 创建一个semaphore 
dispatch_semaphore_signal 发送一个信号 
dispatch_semaphore_wait 等待信号

信号量的主要作用就是控制并发量,信号量为0则阻塞线程,大于0则不会阻塞。我们通过改变信号量的值,来控制是否阻塞线程,从而控制并发控制。
来看个例子

- (void)singal {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);//创建一个值为1的信号量
    NSLog(@"singal ----%zd", index);
    for (int index = 0; index < 5; index++) {
        dispatch_async(queue, ^(){
            NSLog(@"singal ----%zd", index);
            [NSThread sleepForTimeInterval:1];
            dispatch_semaphore_signal(semaphore);//信号量+1
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//试图把信号量-1,如果值小于0,就等待
    }
}

看结果

2017-08-24 17:58:42.057 thread[1326:93346] singal ----1
2017-08-24 17:58:42.057 thread[1326:93349] singal ----0
2017-08-24 17:58:43.062 thread[1326:93346] singal ----2
2017-08-24 17:58:43.063 thread[1326:93349] singal ----3
2017-08-24 17:58:44.065 thread[1326:93349] singal ----4

因为我们创建的信号量初始值为1,然后我们是先执行添加任务道队列,然后-1,所以我们就有两个并发。如果我们创建的信号量为0,或者我们先-1,然后添加任务道队列,那我们就只有一个并发,就等于是同步操作。

一些其他的操作

dispatch_queue_t dispatch_get_current_queue(void);//获取当前队列
// 获取主队列
dispatch_queue_t dispatch_get_main_queue(void){
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}
//队列优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
typedef long dispatch_queue_priority_t;
//获取全局并行队列
/**
 *参数:identifier
 *- DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *- DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
 *- DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *- DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND
 *
 *#define DISPATCH_QUEUE_PRIORITY_HIGH 2
 *#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
 *#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
 *#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
 **/
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
//当指定队列键值改变时,或者是所有属性值都释放后,调用销毁函数destructor
void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key,
void *_Nullable context, dispatch_function_t _Nullable destructor);
//获取队列某个key内容
void *_Nullable dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
//验证当前块任务运行在指定队列上
void dispatch_assert_queue(dispatch_queue_t queue)
DISPATCH_ALIAS_V2(dispatch_assert_queue);
//验证当前块任务运行在指定队列上,并且该任务阻塞队列中的其它任务
void dispatch_assert_queue_barrier(dispatch_queue_t queue);
//验证当前块任务没有运行在指定队列上
void dispatch_assert_queue_not(dispatch_queue_t queue)
DISPATCH_ALIAS_V2(dispatch_assert_queue_not);
功能:获取队列描述信息
参数:label:队列附带信息,可有可无  attr:队列属性值  target:目标队列,相当于目标队列计数加一
返回值:队列附带信息
dispatch_queue_t dispatch_queue_create_with_target(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr, dispatch_queue_t _Nullable target)
//创建时间对象,在指定时间的基础上再添加一段时间
//when:时间  delta:时间段(纳秒)
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
//创建时间对象,在指定时间的基础上再添加一段时间
//when:时间  delta时间段(纳秒)
返回值:时间对象
dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
//增加队列引用计数
void dispatch_retain(dispatch_object_t object);
//减少队列引用计数
void dispatch_release(dispatch_object_t object);
//获取对象应用程序上下文
void *_Nullable dispatch_get_context(dispatch_object_t object);
//设置指定对象的应用程序上下文
void dispatch_set_context(dispatch_object_t object, void *_Nullable context);
//挂起队列
void dispatch_suspend(dispatch_object_t object);
//恢复队列
void dispatch_resume(dispatch_object_t object);
//取消对象
void dispatch_cancel(void *object);
//判断对象是否被取消
long dispatch_testcancel(void *object);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容