iOS开发多线程篇之GCD

1771779-da221054beb5cbb4.png
dispatch_once_t必须是全局或static变量

这一条算是“老生常谈”了,但我认为还是有必要强调一次,毕竟非全局或非static的dispatch_once_t变量在使用时会导致非常不好排查的bug,正确的如下:

//静态变量,保证只有一份实例,才能确保只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
   //单例代码
});

其实就是保证dispatch_once_t只有一份实例。

dispatch_queue_create的第二个参数

dispatch_queue_create,创建队列用的,它的参数只有两个,原型如下:

dispatch_queue_t dispatch_queue_create ( const char *label, dispatch_queue_attr_t attr );

在网上的大部分教程里(甚至Apple自己的文档里),都是这么创建串行队列的:

dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);

看,第二个参数传的是“NULL”。 但是dispatch_queue_attr_t类型是有已经定义好的常量的,所以我认为,为了更加的清晰、严谨,最好如下创建队列:

//串行队列
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_SERIAL);

//并行队列
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_CONCURRENT);

常量就是为了使代码更加“易懂”,更加清晰,既然有,为啥不用呢~

dispatch_after是延迟提交,不是延迟运行
先看看官方文档的说明:

Enqueue a block for execution at the specified time.

Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行!

看看如下代码示例:

//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);

//立即打印一条信息
NSLog(@"Begin add block...");

//提交一个block
dispatch_async(queue, ^{
 //Sleep 10秒
[NSThread sleepForTimeInterval:10];
 NSLog(@"First block done...");
});

//5 秒以后提交block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
 NSLog(@"After...");
});

结果如下:

2015-03-31 20:57:27.122 GCDTest[45633:1812016] Begin add block...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] First block done...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] After...

从结果也验证了,dispatch_after只是延时提交block,并不是延时后立即执行。所以想用dispatch_after精确控制运行状态的朋友可要注意了~

正确创建dispatch_time_t

用dispatch_after的时候就会用到dispatch_time_t变量,但是如何创建合适的时间呢?答案就是用dispatch_time函数,其原型如下:

dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );

第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始。那么第二个参数就是真正的延时的具体时间。

这里要特别注意的是,delta参数是“纳秒!”,就是说,延时1秒的话,delta应该是“1000000000”=。=,太长了,所以理所当然系统提供了常量,如下:

#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull

关键词解释:

  • NSEC:纳秒。
  • USEC:微妙。
  • SEC:秒
  • PER:每
    所以:
  • NSEC_PER_SEC,每秒有多少纳秒。
  • USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
  • NSEC_PER_USEC,每毫秒有多少纳秒。

所以,延时1秒可以写成如下几种:

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);

最后一个“USEC_PER_SEC * NSEC_PER_USEC”,翻译过来就是“每秒的毫秒数乘以每毫秒的纳秒数”,也就是“每秒的纳秒数”,所以,延时500毫秒之类的,也就不难了吧~

dispatch_suspend != 立即停止队列的运行

dispatch_suspenddispatch_resume提供了“挂起、恢复”队列的功能,简单来说,就是可以暂停、恢复队列上的任务。但是这里的“挂起”,并不能保证可以立即停止队列上正在运行的block,看如下例子:

dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);

//提交第一个block,延时5秒打印。
dispatch_async(queue, ^{
   [NSThread sleepForTimeInterval:5];
   NSLog(@"After 5 seconds...");
});

//提交第二个block,也是延时5秒打印  
dispatch_async(queue, ^{ 
  [NSThread sleepForTimeInterval:5];
   NSLog(@"After 5 seconds again...");
});

//延时一秒NSLog(@"sleep 1 second...");
[NSThread sleepForTimeInterval:1];

//挂起队列
NSLog(@"suspend...");
dispatch_suspend(queue);

//延时10秒
NSLog(@"sleep 10 second...");
[NSThread sleepForTimeInterval:10];

//恢复队列NSLog(@"resume...");
dispatch_resume(queue);

运行结果如下:

2015-04-01 00:32:09.903 GCDTest[47201:1883834] sleep 1 second...
2015-04-01 00:32:10.910 GCDTest[47201:1883834] suspend...
2015-04-01 00:32:10.910 GCDTest[47201:1883834] sleep 10 second...2015-04-01 00:32:14.908 GCDTest[47201:1883856] After 5 seconds...
2015-04-01 00:32:20.911 GCDTest[47201:1883834] resume...
2015-04-01 00:32:25.912 GCDTest[47201:1883856] After 5 seconds again...

可知,在dispatch_suspend挂起队列后,第一个block还是在运行,并且正常输出。结合文档,我们可以得知,dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。

所以下次想暂停正在队列上运行的block时,还是不要用dispatch_suspend了吧~

“同步”的dispatch_apply

dispatch_apply的作用是在一个队列(串行或并行)上“运行”多次block,其实就是简化了用循环去向队列依次添加block任务。但是我个人觉得这个函数就是个“坑”,先看看如下代码运行结果:

//创建异步串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);

//运行block3次
dispatch_apply(3, queue, ^(size_t i) {
   NSLog(@"apply loop: %zu", i);
});

//打印信息
NSLog(@"After apply");

运行的结果是:

2015-04-01 00:55:40.854 GCDTest[47402:1893289] apply loop: 0
2015-04-01 00:55:40.856 GCDTest[47402:1893289] apply loop: 1
2015-04-01 00:55:40.856 GCDTest[47402:1893289] apply loop: 2  
2015-04-01 00:55:40.856 GCDTest[47402:1893289] After apply

看,明明是提交到异步的队列去运行,但是“After apply”居然在apply后打印,也就是说,dispatch_apply将外面的线程(main线程)“阻塞”了!

查看官方文档,dispatch_apply确实会“等待”其所有的循环运行完毕才往下执行=。=,看来要小心使用了。

避免死锁!
dispatch_sync导致的死锁
涉及到多线程的时候,不可避免的就会有“死锁”这个问题,在使用GCD时,往往一不小心,就可能造成死锁,看看下面的“死锁”例子:

//在main线程使用“同步”方法提交Block,必定会死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
   NSLog(@"I am block...");
});

你可能会说,这么低级的错误,我怎么会犯,那么,看看下面的:
- (void)updateUI1 {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Update ui 1");

    //死锁!
   [self updateUI2]; });
}

- (void)updateUI2 { 
  dispatch_sync(dispatch_get_main_queue(), ^{ 
    NSLog(@"Update ui 2"); });
}

在你不注意的时候,嵌套调用可能就会造成死锁!所以为了“世界和平”=。=,我们还是少用dispatch_sync吧。

灵活使用dispatch_group

很多时候我们需要等待一系列任务(block)执行完成,然后再做一些收尾的工作。如果是有序的任务,可以分步骤完成的,直接使用串行队列就行。但是如果是一系列并行执行的任务呢?这个时候,就需要dispatch_group帮忙了~总的来说,dispatch_group的使用分如下几步:

  • 创建dispatch_group_t
  • 添加任务(block)
  • 添加结束任务(如清理操作、通知UI等)

下面着重讲讲在后面两步。

添加任务

添加任务可以分为以下两种情况:

1、自己创建队列:使用dispatch_group_async
2、无法直接使用队列变量(如使用AFNetworking添加异步任务):使用dispatch_group_enterdispatch_group_leave

自己创建队列时,当然就用dispatch_group_async函数,简单有效,简单例子如下:

dispatch_group_t group = dispatch_group_create();
//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{ 
  //Do you work...
});

当你无法直接使用队列变量时,就无法使用dispatch_group_async了,下面以使用AFNetworking时的情况:

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

//Enter group
dispatch_group_enter(group);
[manager GET:@"http://www.baidu.com" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
 //Deal with result...

 //Leave group
 dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
 //Deal with error...

//Leave group
 dispatch_group_leave(group);
}];

使用dispatch_group_enterdispatch_group_leave就可以方便的将一系列网络请求“打包”起来~

添加结束任务

添加结束任务也可以分为两种情况,如下:
1、在当前线程阻塞的同步等待:dispatch_group_wait。

//(2)监视函数 dispatch_group_wait  
// 等待 time 时间后 对队列进行监听   

dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 5ull *NSEC_PER_SEC);   

long result=dispatch_group_wait(group, time); 
if (result ==0) {   
   NSLog(@"finish");   
}else {     
   NSLog(@"not finish");  
}

2、添加一个异步执行的任务作为结束任务:dispatch_group_notify

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

推荐阅读更多精彩内容