曾静的博客

但行好事,莫问前程.

嗨,我是曾静 (@devzeng),目前暂居深圳。


这是我用来记录平日学习笔记的地方,欢迎您的访问.

iOS多线程之GCD

Grand Central Dispatch(简称GCD)是Apple在Mac OS X 10.6iOS4.0中首次引入的一个多核并行运算的解决方案。GCD是基于C语言的,如果使用GCD完全由系统管理线程,开发者不需要编写线程代码,只需专注定义想要只需的任务,然后添加到适当的Dispatch Queue(调度队列)。

GCD是一个可用来替代NSThread、NSOperation、NSOperataionQueue等技术的一个非常强大的解决方案。GCD在工作时会自动利用更多的处理器核心以充分利用机器的可用资源。

Dispatch Queue

GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用。所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致。

1、创建队列

(1)创建自定义串行队列

dispatch_queue_t queue = dispatch_queue_create("com.devzeng.gcd.serial", DISPATCH_QUEUE_SERIAL); 生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。

(2)创建自定义并发队列

dispatch_queue_t queue = dispatch_queue_create("com.devzeng.gcd.concurrent", DISPATCH_QUEUE_CONCURRENT); 生成一个并发执行队列,block被分发到多个线程去执行

(3)创建默认缺省的并发队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。

官网文档解释说共有三个并发队列,但实际还有一个更低优先级的队列,设置优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode调试时可以观察到正在使用的各个dispatch队列。

(4) 获取主线程的队列

dispatch_queue_t queue = dispatch_get_main_queue(); 获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。

2、使用队列

创建好队列后,使用dispatch_asyncdispatch_sync这两个函数可以执行Block代码。Block函数不返回,一直等到block执行完毕。编译器会根据实际情况优化代码,所以有时候你会发现block其实还在当前线程上执行,并没用产生新线程。

(1)异步执行block,函数立即返回

dispatch_async(queue, ^{
  //block具体代码
});

(2)同步执行block

dispatch_sync(queue, ^{
  //block具体代码
});

(3)避免死锁

实际编程经验告诉我们,尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。如果queue是一个串行队列的话,这段代码立即产生死锁:

dispatch_sync(queue, ^{
	dispatch_sync(queue, ^{
		//
	});
});

同样下面的代码也会出现死锁:

dispatch_sync(dispatch_get_main_queue(), ^{
  //......
}); 

(4)最佳实践

1)网络请求数据处理示例:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  //子线程中开始网络请求数据
  //更新数据模型
  dispatch_sync(dispatch_get_main_queue(), ^{
    //在主线程中更新UI代码
  });
});

2)使用串行队列

dispatch队列是线程安全的,可以利用串行队列实现锁的功能。比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性,简单地利用串行队列即可实现:

dispatch_queue_t queue1 = dispatch_queue_create("com.devzeng.dispatch.writedb", DISPATCH_QUEUE_SERIAL);

- (void)writeDB:(NSData *)data
{
  dispatch_async(queue1, ^{
    //write database
  });
} 

下一次调用writeDB:必须等到上次调用完成后才能进行,保证writeDB:方法是线程安全的。

(5)dispatch队列还实现其它一些常用函数

1)重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

2)这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

3)同上,除了它是同步返回函数

void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

4)延迟执行block

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾。

Dispatch Group

1、创建dispatch组

dispatch_group_t group = dispatch_group_create();

2、启动dispatch队列中的block关联到group中

dispatch_group_async(group, queue, ^{ 
  // 。。。 
}); 

3、等待group关联的block执行完毕,也可以设置超时参数

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

4、为group设置通知一个block,当group关联的block执行完毕后,就调用这个block。类似dispatch_barrier_async。

dispatch_group_notify(group, queue, ^{
  // 。。。 
}); 

5、手动管理group关联的block的运行状态(或计数),进入和退出group次数必须匹配

dispatch_group_enter(group);dispatch_group_leave(group);

通常使用的下面的写法

dispatch_group_async(group, queue, ^{ 
  // 。。。 
}); 

等价于

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

所以,可以利用dispatch_group_enter、 dispatch_group_leave和dispatch_group_wait来实现同步,具体例子

参考资料

1、《iOS多线程的初步研究(七)– dispatch对象》

2、《iOS多线程的初步研究(八)– dispatch队列》

3、《iOS多线程的初步研究(九)– dispatch源》

4、《iOS多线程的初步研究(十)– dispatch同步》

5、《多线程编程4 - GCD》

6、《使用GCD》

最近的文章

iOS开发之同一应用设置不同图标和名称

经常在开发中遇到同一个App会有很多渠道版本,比如OTA内部测试版本,AppStore发布版本等。针对这些不同的版本我们通常会选择不同的图标、应用名称等,效果如下图所示:P.S上面使用的两个测试图标分别来源于土巴兔和乐视TV的iPhone版本APP的图标,是两个非常不错的APP,感谢你们。Bundle IDiOS系统区分不同的App是否相同是根据App的Bundle ID是否相同来判断的。如果想要在一个系统上安装一个App的多个版本其实是需要多个Bundle ID,就是说正式版一个Bund...…

iOS继续阅读
更早的文章

iOS开发之多系统版本兼容

Apple自2007年发布第一版iOS操作系统以来,差不多以后的每年都会发布新版的操作系统,从最开始的iPhone OS到现在即将正式发布的iOS8。目前市面上能见到的iOS的版本主要是iOS4.3、iOS5、iOS6、iOS7,在实际的开发中遇到很多用户由于各种各样的原因没有升级到最新版,这就给我们开发者带了不少的麻烦。不过值得高兴的是截止到2014年8月24日,目前市面上大部分的iOS设备已经更新到iOS7了(91% of devices are using iOS 7),对于一些占小...…

iOS继续阅读