原文链接:http://www.cnblogs.com/mddblog/p/4767559.html  

  敲下gcd三个字母,搜狗第一条显示居然是“滚床单” ^_^

一、介绍

  GCD,英文全称是Grand Central Dispatch(功能强悍的中央调度器),基于C语言编写的一套多线程开发机制,因此使用时会以函数形式出现,且大部分函数以dispatch开头,虽然是C语言的但相对于苹果其它多线程实现方式,抽象层次更高,使用起来也更加方便。

  它是苹果为应对多核的并行运算提出的解决方案,它会自动利用多核进行并发处理和运算,且线程由系统自动管理(调度、运行),无需程序员参与,使用起来非常方便。

二、任务和队列

  GCD有两个核心:任务和队列。

  任务:要执行的操作或方法函数,队列:存放任务的集合,而我们要做的就是将任务添加到队列然后执行,GCD会自动将队列中的任务按先进先出的方式取出并交给对应线程执行。注意任务的取出是按照先进先出的方式,这也是队列的特性,但是取出后的执行顺序则不一定,下面会详细讨论。

  1 任务

  任务是一个比较抽象的概念,可以简单的认为是一个操作、一个函数、一个方法等等,在实际的开发中大多是以block(block使用详见)的形式,使用起来也更加灵活。

  2 队列queue

  • 有两种队列:串行队列和并行队列。串行队列:同步执行,在当前线程执行;并行队列:可由多个线程异步执行,但任务的取出还是FIFO的。

    队列创建,根据函数第二个参数来创建串行或并行队列。

// 参数1 队列名称
// 参数2 队列类型 DISPATCH_QUEUE_SERIAL/NULL串行队列,DISPATCH_QUEUE_CONCURRENT代表并行队列
// 下面代码为创建一个串行队列,也是实际开发中用的最多的
dispatch_queue_t serialQ = dispatch_queue_create("队列名", NULL);
  • 另外系统提供了两种队列:全局队列和主队列。

    全局队列属于并行队列,只不过已由系统创建的没有名字,且在全局可见(可用)。获取全局队列:

/* 取得全局队列
第一个参数:线程优先级,设为默认即可,个人习惯写0,等同于默认
第二个参数:标记参数,目前没有用,一般传入0
*/
serialQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );

    主队列属于串行队列,也由系统创建,只不过运行在主线程(UI线程)。获取主队列:

// 获取主队列
serialQ = dispatch_get_main_queue();
  • 关于内存

    queue属于一个对象,也是占用内存的,也会使用引用计数,当向queue添加一个任务时就会将这个queue retain一下,引用计数+1,直到所有任务都完成内存才会释放。(我们在声明一个queue属性时要用strong)。

  3 执行方式-2种

  同步执行和异步执行。

    同步执行:不会开启新的线程,在当前线程执行。

    异步执行:gcd管理的线程池中有空闲线程就会从队列中取出任务执行,会开启线程。

  下面为实现同步和异步的函数,函数功能为:将任务添加到队列并执行。

/* 同步执行
第一个参数:执行任务的队列:串行、并行、全局、主队列
第二个参数:block任务
*/
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 异步执行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

注意:默认情况下,新线程都没有开启runloop,所以当block任务完成后,线程都会自动被回收,假设我们想在新开的线程中使用NSTimer,就必须开启runloop,可以使用[[NSRunLoop currentRunLoop] run]开启当前线程,这是就要自己管理线程的回收等工作。

  • 另外还有两个方法,实际开发中用的并不是太多
dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

  加了一个barrier,意义在于:队列之前的block处理完成之后才开始处理队列中barrier的block,且barrier的block必须处理完之后,才能处理其它的block。

  根据这个特性我们可以实现123456一共6个block,可以让特定几个并发执行完成之后,再并发执行剩下的block。比如123先并发,之后456再并发执行。具体代码如下(将barrier放在123与456之间即可):

- (void)barrierTest {
// 1 创建并发队列
dispatch_queue_t BCqueue = dispatch_queue_create("BarrierConcurrent", DISPATCH_QUEUE_CONCURRENT); // 2.1 添加任务123
dispatch_async(BCqueue, ^{
NSLog(@"task1,%@", [NSThread currentThread]);
});
dispatch_async(BCqueue, ^{
sleep();
NSLog(@"task2,%@", [NSThread currentThread]);
});
dispatch_async(BCqueue, ^{
sleep();
NSLog(@"task3,%@", [NSThread currentThread]);
});
// 2.2 添加barrier
dispatch_barrier_async(BCqueue, ^{
NSLog(@"barrier");
});
// 2.3 添加任务456
dispatch_async(BCqueue, ^{
sleep();
NSLog(@"task4,%@", [NSThread currentThread]);
});
dispatch_async(BCqueue, ^{
NSLog(@"task5,%@", [NSThread currentThread]);
});
dispatch_async(BCqueue, ^{
NSLog(@"task6,%@", [NSThread currentThread]);
});
}

  输出结果,为了显示效果,代码有延时操作:

::56.822 GCDTest[:] task1,<NSThread: 0x600000274780>{number = , name = (null)}
::57.827 GCDTest[:] task3,<NSThread: 0x600000271a00>{number = , name = (null)}
::59.826 GCDTest[:] task2,<NSThread: 0x600000275100>{number = , name = (null)}
::59.826 GCDTest[:] barrier
::59.827 GCDTest[:] task5,<NSThread: 0x600000271a00>{number = , name = (null)}
::59.827 GCDTest[:] task6,<NSThread: 0x608000279300>{number = , name = (null)}
::00.828 GCDTest[:] task4,<NSThread: 0x600000275100>{number = , name = (null)}

三、几种类型

  很明显两种执行方式,两种队列。那么就有4种情况:串行队列同步执行、串行队列异步执行、并行队列同步执行、并行队列异步执行。哪一种会开启新的线程?开几条?是否并发?记忆起来比较绕,但是只要抓住基本的就可以,为了方便理解,现分析如下:

  1)串行队列,同步执行-----串行队列意味着顺序执行,同步执行意味着不开启线程(在当前线程执行)

  2)串行队列,异步执行-----串行队列意味着任务顺序执行,异步执行说明要开线程, (如果开多个线程的话,不能保证串行队列顺序执行,所以只开一个线程)

  3)并行队列,异步执行-----并行队列意味着执行顺序不确定,异步执行意味着会开启线程,而并行队列又允许不按顺序执行,所以系统为了提高性能会开启多个线程,来队列取任务(队列中任务取出仍然是顺序取出的,只是线程执行无序)。

  4)并行队列,同步执行-----同步执行意味着不开线程,则肯定是顺序执行

  5)死锁-----程序执行不出来(死锁) ;

四、死锁举例

  • 主队列死锁:

    这种死锁最常见,问题也最严重,会造成主线程卡住。原因:主队列,如果主线程正在执行代码,就不调度任务;同步执行:一直执行第一个任务直到结束。两者互相等待造成死锁,示例如下:

- (void)mainThreadDeadLockTest {
NSLog(@"begin");
dispatch_sync(dispatch_get_main_queue(), ^{
// 发生死锁下面的代码不会执行
NSLog(@"middle");
});
// 发生死锁下面的代码不会执行,当然函数也不会返回,后果也最为严重
NSLog(@"end");
}
  • 在其它线程死锁,这种不会影响主线程:

    原因:serialQueue为串行队列,当代码执行到block1时正常,执行到dispatch_sync时,dispatch_sync等待block2执行完毕才会返回,而serialQueue是串行队列,它正在执行block1,只有等block1执行完毕后才会去执行block2,相互等待造成死锁

- (void)deadLockTest {
// 其它线程的死锁
dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
// 串行队列block1
NSLog(@"begin");
dispatch_sync(serialQueue, ^{
// 串行队列block2 发生死锁,下面的代码不会执行
NSLog(@"middle");
});
// 不会打印
NSLog(@"end");
});
// 函数会返回,不影响主线程
NSLog(@"return");
}

五、常用举例

  1 线程间通讯

  比如,为了提高用户体验,我们一般在其他线程(非主线程)下载图片或其它网络资源,下载完成后我们要更新UI,而UI更新必须在主线程执行,所以我们经常会使用:

// 同步执行,会阻塞指导下面block中的代码执行完毕
dispatch_sync(dispatch_get_main_queue(), ^{
// 主线程,UI更新
});
// 异步执行
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程,UI更新
});

  2 信号量的使用

也属于线程间通讯,下面的举例是经常用到的场景。在网络访问中,NSURLSession类都是异步的(找了很久没有找到同步的方法),而有时我们希望能够像NSURLConnection一样可以同步访问,即在网络block调用完成之后做一些操作。那我们可以使用dispatch的信号量来解决:

/// 用于线程间通讯,下面是等待一个网络完成
- (void)dispatchSemaphore {
NSString *urlString = [@"https://www.baidu.com" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
// 设置缓存策略为每次都从网络加载 超时时间30秒
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:];
dispatch_semaphore_t semaphore = dispatch_semaphore_create();
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 处理完成之后,发送信号量
NSLog(@"正在处理...");
dispatch_semaphore_signal(semaphore);
}] resume];
// 等待网络处理完成
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"处理完成!");
}

在上面的举例中dispatch_semaphore_signal的调用必须是在另一个线程调用,因为当前线程已经dispatch_semaphore_wait阻塞。另外,dispatch_semaphore_wait最好不要在主线程调用

  3 其它常用

  全局队列,实现并发:

dispatch_async(dispatch_get_global_queue(, ), ^{
// 要执行的代码
});

六、Dispatch Group调度组

  使用调度组,可以轻松实现在一些任务完成后,做一些操作。比如具有顺序性要求的生产者消费者等等。

  示例1  任务1完成之后执行任务2。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self groupTest];
}
- (void)groupTest {
// 创建一个组
dispatch_group_t group = dispatch_group_create();
NSLog(@"开始执行");
dispatch_async(dispatch_get_global_queue(, ), ^{
dispatch_group_async(group, dispatch_get_global_queue(, ), ^{
// 任务1
// 等待1s一段时间在执行
[NSThread sleepForTimeInterval:];
NSLog(@"task1 running in %@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_global_queue(, ), ^{
// 任务2
NSLog(@"task2 running in %@",[NSThread currentThread]);
});
});
}

  点击屏幕后,打印如下,可以看到任务1虽然等待了1s,任务2也不执行,只有任务1执行完毕才执行任务2.

-- ::05.317 GCDTest[:] 开始执行
-- ::06.323 GCDTest[:] task1 running in <NSThread: 0x7f8962f16900>{number = , name = (null)}
-- ::06.323 GCDTest[:] task2 running in <NSThread: 0x7f8962c92750>{number = , name = (null)}

  示例2,其实示例1并不常用,真正用到的是监控多个任务完成之后,回到主线程更新UI,或者做其它事情。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self groupTest];
}
- (void)groupTest {
// 创建一个组
dispatch_group_t group = dispatch_group_create();
NSLog(@"开始执行");
dispatch_async(dispatch_get_global_queue(, ), ^{
dispatch_group_async(group, dispatch_get_global_queue(, ), ^{
// 关联任务1
NSLog(@"task1 running in %@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(, ), ^{
// 关联任务2
NSLog(@"task2 running in %@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(, ), ^{
// 关联任务3
NSLog(@"task3 running in %@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(, ), ^{
// 关联任务4
// 等待1秒
[NSThread sleepForTimeInterval:];
NSLog(@"task4 running in %@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 回到主线程执行
NSLog(@"mainTask running in %@",[NSThread currentThread]);
});
});
}

  点击屏幕后,打印如下,可以看到无论其它任务然后和执行,mainTask等待它们执行后才执行。

-- ::14.312 GCDTest[:] 开始执行
-- ::14.312 GCDTest[:] task3 running in <NSThread: 0x7fa8f1f0c9c0>{number = , name = (null)}
-- ::14.312 GCDTest[:] task1 running in <NSThread: 0x7fa8f1d10750>{number = , name = (null)}
-- ::14.312 GCDTest[:] task2 running in <NSThread: 0x7fa8f1c291a0>{number = , name = (null)}
-- ::15.313 GCDTest[:] task4 running in <NSThread: 0x7fa8f1d0e7f0>{number = , name = (null)}
-- ::15.313 GCDTest[:] mainTask running in <NSThread: 0x7fa8f1c13df0>{number = , name = main}

关于Dispatch对象内存管理问题

根据上面的代码,可以看出有关dispatch的对象并不是OC对象,那么,用不用像对待Core Foundation框架的对象一样,使用retain/release来管理呢?答案是不用的!

如果是ARC环境,我们无需管理,会像对待OC对象一样自动内存管理。
如果是MRC环境,不是使用retain/release,而是使用dispatch_retain/dispatch_release来管理。

测试用例github下载:https://github.com/mddios/GCDTest

iOS多线程实现3-GCD的更多相关文章

  1. iOS多线程知识总结--GCD

    iOS多线程知识总结--GCD 1. iOS中苹果提供4钟方案来帮助我们实现多线程: (1) 纯C语言的pthread,偏底层,需要程序员手动管理线程的生命周期,基本不用. (2) OC语言的NSTr ...

  2. iOS多线程开发之GCD(中篇)

    前文回顾: 上篇博客讲到GCD的实现是由队列和任务两部分组成,其中获取队列的方式有两种,第一种是通过GCD的API的dispatch_queue_create函数生成Dispatch Queue:第二 ...

  3. iOS多线程开发之GCD(死锁篇)

    上篇和中篇讲解了什么是GCD,如何使用GCD,这篇文章将讲解使用GCD中将遇到的死锁问题.有兴趣的朋友可以回顾<iOS多线程开发之GCD(上篇)>和<iOS多线程开发之GCD(中篇) ...

  4. iOS多线程开发之GCD(中级篇)

    前文回顾: 上篇博客讲到GCD的实现是由队列和任务两部分组成,其中获取队列的方式有两种,第一种是通过GCD的API的dispatch_queue_create函数生成Dispatch Queue:第二 ...

  5. iOS多线程开发之GCD(基础篇)

    总纲: GCD基本概念 GCD如何实现 GCD如何使用 队列和任务组合 一.GCD基本概念 GCD 全称Grand Central Dispatch(大中枢队列调度),是一套低层API,提供了⼀种新的 ...

  6. iOS 多线程学习笔记 —— GCD

    本文复制.参考自文章:iOS多线程编程之Grand Central Dispatch(GCD)介绍和使用 ,主要为了加强个人对知识的理解和记忆,不做他用.原作者声明: 著作权声明:本文由http:// ...

  7. iOS 多线程:『GCD』详尽总结

    本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法.这大概是史上最详细.清晰的关于 GCD 的详细讲解+总结的文章了.通过本文,您将了解到: 1. GCD 简介 2. GCD 任务和队列 ...

  8. iOS多线程编程之GCD的使用

    什么是线程呢? 1个CPU执行的CPU命令列为一条无分叉的路径即为线程. 这种无分叉路径不止1条,存在多条时即为多线程. 什么是GCD? Grand Central Dispatch (GCD)是异步 ...

  9. iOS多线程NSThread和GCD

    在iOS中啊  其实有多种方法实现多线程 这里只记录两个比较常用的  或者说我比较常用的 一个就是BSThread 另一个就是一听名字就比较霸气的妇孺皆知的GCD 先说一下NSThread吧 这个方式 ...

  10. IOS 多线程05-OperationQueue 、GCD详解

      注:本人是翻译过来,并且加上本人的一点见解. 1. 开始 目前在 iOS中有两套先进的同步 API 可供我们使用:操作队列OperationQueue和 GCD .其中 GCD 是基于 C 的底层 ...

随机推荐

  1. Elasticsearch、Logstash、Kibana搭建统一日志分析平台

    // // ELKstack是Elasticsearch.Logstash.Kibana三个开源软件的组合.目前都在Elastic.co公司名下.ELK是一套常用的开源日志监控和分析系统,包括一个分布 ...

  2. 使用GIT@OSChina 实现协同工作的方法。

    由于我新建了一个团队,团队里的人对于GIT都不太熟悉,所以才有了这篇文章.我用的是git-1.9.4的版本,所以我建议团队里面的成员也使用这个版本.首先是下载git,这个自己去网上找吧,一大堆,记得是 ...

  3. Android PopupWindow怎么合理控制弹出位置(showAtLocation)

    说到PopupWindow,应该都会有种熟悉的感觉,使用起来也很简单 // 一个自定义的布局,作为显示的内容 Context context = null; // 真实环境中要赋值 int layou ...

  4. 在.net中读写config文件的各种方法

    阅读目录 开始 config文件 - 自定义配置节点 config文件 - Property config文件 - Element config文件 - CDATA config文件 - Collec ...

  5. 简述.NET事务应用原则

    .NET事务应用原则 1.在同一个数据库内进行CRUD时,应使用同一个DbConnection对象,且显式指定DbConnection均为同一个DbTransaction,示例代码如下: //在同一个 ...

  6. jQuery 判断是否包含某个属性

    1.Get the attribute, check the value var attr = $(this).attr('name'); // For some browsers, `attr` i ...

  7. EF架构~EF异步改造之路~让DbContextRepository去实现异步接口

    回到目录 返回异步与并行目录 上一讲中,我们定义了三个异步操作接口,这回我们将对它进行实现,而有一个基础知识需要大家清楚,那就是实现接口的方式,一般我们使用默认的方式(隐式实现),这种方法实现的接口方 ...

  8. [moka同学摘录]SQL内联、外联的简单理解

    (源自:http://blog.csdn.net/kkk9127/article/details/1487686) --查询分析器中执行:--建表table1,table2:create table ...

  9. androidstudio报错

    今天换一下IDE,结果上来就给我报错, androidstudio message:Error:java.lang.UnsupportedClassVersionError: com/android/ ...

  10. 从零开始学 Java - CentOS 安装 JDK

    我来总结一下吧 昨天我写了一篇从零开始学 Java - 我放弃了 .NET ?,在园子里突然引起了强烈的讨论,有期待我能持续更新的.有鼓励支持的.有相同经历的.也有同一个学校的师兄弟(我们相认了).当 ...