iOS 多线程之 GCD

GCD全称Grand Central Dispatch 。应用程序以Block块对象的形式提交任务到队列,GCD提供并管理的这些队列。 提交到GCD队列的工作在完全在系统管理的线程池上执行。
GCD 的一些常用方法:

Dispatch Queue

1. dispatch_get_main_queue

返回主线程,由系统创建,是一个串行队列。

2. dispatch_get_global_queue
 dispatch_get_global_queue(long identifier, unsigned long flags);

返回具有指定优先级的系统定义的全局并发队列。其中flags参数为保留位,供将来使用, 传递除零以外的任何值可能会导致 NULL返回值。
优先级分为四种:高、默认、低以及后台

#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

对应 Qos的优先级为:

dispatch_queue_priority_t QoS
HIGH NSQualityOfServiceUserInitiated
DEFAULT NSQualityOfServiceDefault
LOW NSQualityOfServiceUtility
BACKGROUND NSQualityOfServiceBackground
  • QOS_CLASS_USER_INTERACTIVE
    最高优先级,即使在争用情况下也可以运行几乎所有可用的系统CPU和I / O带宽。所以使用时应限于与用户的关键交互,例如处理主事件循环上的事件,视图绘制,动画等。
  • QOS_CLASS_USER_INITIATED
    低于用户的关键交互,但相对高于系统上的其他工作,使用于持续时间短的操作
  • QOS_CLASS_UTILITY
    用于一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载任务等
  • QOS_CLASS_BACKGROUND
    用于完全不紧急的任务,磁盘 I/O后台备份等用这个,使用此QOS类表明工作应以最节能和最有效的方式运行
  • QOS_CLASS_DEFAULT
    优先级介于user-initiated 和 utility,由pthread_create()创建的线程没有指定QOS的属性将默认为QOS_CLASS_DEFAULT, 此值不应用作工作分类,只应在传播或恢复系统提供的QOS类值时进行设置
3. dispatch_queue_create
dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

其中label:队列名称,用于debug时唯一标示此队列,建议使用反向DNS命名样式(com.example.myqueue),因为应用程序、库和框架等都可以创建自己的队列
attr:DISPATCH_QUEUE_SERIAL or NULL为串行队列,DISPATCH_QUEUE_CONCURRENT为并行队列
iOS 8之后可以使用dispatch_queue_attr_make_with_qos_class方法来生成dispatch_queue_attr_t

dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t _Nullable attr,
        dispatch_qos_class_t qos_class, int relative_priority)

relative_priority为相对优先级(相对于指定优先级的负偏移量,因为优先级qos_class实际就是一个 int的枚举),传递大于零或小于QOS_MIN_RELATIVE_PRIORITY(-15)的值会导致返回NULL;一般直接设置为0
还可以通过dispatch_set_target_queue设置优先级

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MyQueue",NULL);  
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);  
//serialQueue现在的优先级跟globalQueue的优先级一样
dispatch_set_target_queue(serialQueue, globalQueue);

这个方法的前提是线程创建时没有指定优先级,但是系统还是建议使用dispatch_queue_attr_make_with_qos_class来设置优先级
另外,dispatch_set_target_queue有一个属性,就是如果一个串行队列,他的目标队列是另一个串行队列,那么提交到这个串行队列的 block块不会与提交到目标队列或者具有相同目标队列的任何其他队列的block块同时调用,比较绕口,如下代码:

dispatch_queue_t targetQueue = dispatch_queue_create("com.yxw.target_queue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("com.yxw.queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("com.yxw.queue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"target do job1");
        [NSThread sleepForTimeInterval:5.f];
    });
    dispatch_async(queue2, ^{
        NSLog(@"target do job2");
        [NSThread sleepForTimeInterval:2.f];
    });
    dispatch_async(queue2, ^{
        NSLog(@"target do job3");
        [NSThread sleepForTimeInterval:1.f];
    });
    dispatch_async(targetQueue, ^{
        NSLog(@"target do job4");
        [NSThread sleepForTimeInterval:2.f];
    });

执行结果:

YMultiThreadDemo[4068:382944] target do job1
YMultiThreadDemo[4068:382944] target do job2
YMultiThreadDemo[4068:382944] target do job3
YMultiThreadDemo[4068:382944] target do job4
4. dispatch_barrier_async

dispatch_barrier_async用于等待队列前面的任务执行完毕后自己才执行,而它后面的任务也需等待它完成之后才执行。如数据的读写:

dispatch_queue_t dbQueue = dispatch_queue_create("com.yxw.database", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(dbQueue, ^{
        NSLog(@"DB reading data1");
        [NSThread sleepForTimeInterval:1.];
        NSLog(@"DB read data1 completed");
    });
    dispatch_async(dbQueue, ^{
        NSLog(@"DB reading data2");
        [NSThread sleepForTimeInterval:2.];
        NSLog(@"DB read data2 completed");
    });
    dispatch_barrier_async(dbQueue, ^{
        NSLog(@"DB writing data1");
        [NSThread sleepForTimeInterval:1.];
        NSLog(@"DB write data1 completed");
    });
    dispatch_async(dbQueue, ^{
        NSLog(@"DB reading data3");
        [NSThread sleepForTimeInterval:1.];
    });

执行结果:

YMultiThreadDemo[4239:399611] DB reading data1
YMultiThreadDemo[4239:399637] DB reading data2
YMultiThreadDemo[4239:399611] DB read data1 completed
YMultiThreadDemo[4239:399637] DB read data2 completed
YMultiThreadDemo[4239:399637] DB writing data1
YMultiThreadDemo[4239:399637] DB write data1 completed
YMultiThreadDemo[4239:399637] DB reading data3
5. dispatch_queue_set_specific 、dispatch_get_specific
dispatch_queue_set_specific(dispatch_queue_t queue, const void *key,
        void *_Nullable context, dispatch_function_t _Nullable destructor)
dispatch_queue_get_specific(dispatch_queue_t queue, const void *key)

dispatch_queue_set_specific用系统唯一的key将特定的上下文与queue关联,dispatch_queue_get_specific则是通过这个key返回关联的上下文,如:

dispatch_queue_set_specific(queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
YGCDViewController *wself =
    (__bridge id)dispatch_queue_get_specific(queue, kDispatchQueueSpecificKey);
    NSLog(@"get specific wself title %@",wself.navigationItem.title);
6. dispatch_apply
dispatch_apply(size_t iterations,
       dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
       DISPATCH_NOESCAPE void (^block)(size_t))

dispatch_apply类似一个循环,在指定的queue中执行iterations后返回,如果指定的队列是并发的,则会同时调用block块,这样就要求block是可重入的

dispatch_apply(5, serialQueue, ^(size_t i) {
        NSLog(@"apply run %zi times",i);
});

执行结果:

YMultiThreadDemo[4961:481865] apply run 0 times
YMultiThreadDemo[4961:481865] apply run 1 times
YMultiThreadDemo[4961:481865] apply run 2 times
YMultiThreadDemo[4961:481865] apply run 3 times
YMultiThreadDemo[4961:481865] apply run 4 times

Dispatch Block

dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);

返回的块已经由Block_copy拷贝到堆中,返回块的第一次执行完成才会响应dispatch_block_wait()或者dispatch_block_notify()方法
flags参数:

  • DISPATCH_BLOCK_BARRIER ,提交到并行队列时,这个标志相当于dispatch_barrier_async()
  • DISPATCH_BLOCK_INHERIT_QOS_CLASS,异步提交到队列时默认使用
  • DISPATCH_BLOCK_ENFORCE_QOS_CLASS,同步提交到队列时默认使用
1. dispatch_block_wait 与 dispatch_block_notify
dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout)
dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
        dispatch_block_t notification_block)

dispatch_block_wait:等待指定的block块执行完,或者指定的timeout已经超出,这个方法会阻塞当前线程,所以不要放在主线程中,另外,用dispatch_block_cancel方法取消的 block也算执行完成
dispatch_block_notify:功能跟上面方法类似,等待指定的block块执行完,然后在queue中执行notification_block

2. dispatch_block_cancel

iOS 8 之后,可以通过dispatch_block_cancel方法取消待执行的block,已经在执行的block没有影响,与取消block相关联的任何资源将被延迟释放,直到下一次尝试执行该block
上面三个方法的代码如下:

    dispatch_queue_t queue = dispatch_queue_create("com.yxw.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"before block sleep");
        [NSThread sleepForTimeInterval:2.];
        NSLog(@"after block sleep");
    });
    dispatch_block_t block2 = dispatch_block_create(0, ^{
        NSLog(@"before block2 sleep");
        [NSThread sleepForTimeInterval:10.];
        NSLog(@"after block2 sleep");
    });
    dispatch_async(queue, block);
    dispatch_async(queue, block2);
    
    dispatch_async(globalQueue, ^{
        //等待block执行完毕
        dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
        NSLog(@"block wait coutinue");
    });
    dispatch_block_notify(block, globalQueue, ^{
         NSLog(@"block notify coutinue");
    });
    
    dispatch_async(globalQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        dispatch_block_cancel(block2);
        dispatch_block_cancel(block);
    });
    
    dispatch_block_notify(block2, globalQueue, ^{
        NSLog(@"block2 notify coutinue");
    });

执行结果如下:

YMultiThreadDemo[2690:227780] before block sleep
YMultiThreadDemo[2690:227780] after block sleep
YMultiThreadDemo[2690:227780] block notify coutinue
YMultiThreadDemo[2690:227959] block wait coutinue
YMultiThreadDemo[2690:227962] block2 notify coutinue

可以看到,block2取消成功,block2notify执行了,block取消无效
下面的代码测试只有block的第一次执行完成才会notify

    dispatch_block_t block3 = dispatch_block_create(0, ^{
        NSLog(@"before block3 sleep");
        [NSThread sleepForTimeInterval:1.];
        NSLog(@"after block3 sleep");
    });
    dispatch_async(globalQueue, block3);
    dispatch_block_notify(block3, globalQueue, ^{
        NSLog(@"block3 notify coutinue");
    });
    dispatch_async(globalQueue, ^{
        [NSThread sleepForTimeInterval:3.];
        block3();
    });

执行结果:

YMultiThreadDemo[4569:356960] before block3 sleep
YMultiThreadDemo[4569:356960] after block3 sleep
YMultiThreadDemo[4569:356960] block3 notify coutinue
YMultiThreadDemo[4569:356961] before block3 sleep
YMultiThreadDemo[4569:356961] after block3 sleep

Dispatch Group

当我们想在gcd queue中所有的任务执行完毕之后做些特定事情的时候,也就是队列的同步问题,如果队列是串行的话,那将该操作最后添加到队列中即可,但如果队列是并行队列的话,这时候就可以利用dispatch_group来实现了,dispatch_group能很方便的解决同步的问题。dispatch_group_create可以创建一个group对象,然后可以添加block到该组里面,下面看下它的一些用法:

1. dispatch_group_wait 与 dispatch_group_notify

跟上面的dispatch_block_waitdispatch_block_notify类似,但这里是等待前面提交到groupblock执行完
代码如下:

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group,globalQueue,^{ NSLog(@"group block1"); });
    dispatch_group_async(group,globalQueue,^{ NSLog(@"group block2"); });
    dispatch_async(globalQueue, ^{
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"group wait done");
    });
    dispatch_group_notify(group, globalQueue, ^{
        NSLog(@"group notify done");
    });

执行结果:

YMultiThreadDemo[4877:387857] group block1
YMultiThreadDemo[4877:387857] group block2
YMultiThreadDemo[4877:387857] group wait done
YMultiThreadDemo[4877:387858] group notify done
2. dispatch_group_enter 与 dispatch_group_leave

假如我们不想使用dispatch_group_async异步的将任务丢到group中去执行,这时候就需要用到dispatch_group_enter跟dispatch_group_leave方法,这两个方法要配对出现,以下这两种方法是等价的:

dispatch_group_async(group, queue, ^{ 
}); 
dispatch_group_enter(group);
dispatch_async(queue, ^{
  dispatch_group_leave(group);
});

dispatch_once

dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block);

在 APP 生命周期内,保证只执行一次。一般用于单例的初始化。如果从多个线程同时调用,则此函数将同步等待,所以能保证唯一单例。
predicatedispatch_once一起使用的谓词。 它必须初始化为零,而且必须是静态或全局变量。因为dispatch_once_t实际就是long类型,所以传进去的是指向dispatch_once_t的指针,用于表示block块是否已完成(执行完predicate为-1)。
如下:

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    });
    NSLog(@"onceToken %zi",onceToken);

执行完后onceToken为-1,表示执行完,下次调用就不会再执行block块了。

参考文献
https://developer.apple.com/documentation/dispatch?language=objc

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

推荐阅读更多精彩内容