GCD
来源:互联网 发布:后拱辰享雪面霜知乎 编辑:程序博客网 时间:2024/05/22 00:27
概述
GCD是并发编程的发展方向,对于应用程序来说,GCD消除了线程的概念,甚至不需要考虑太多并发的问题,GCD的模型是基于任务的。
多线程主要提供两大好处:
- 避免阻塞主线程(通过开一个子线程来实现)
- 提高程序整体性能(通过开多个子线程来实现)
而GCD是:
通过下面的方法来实现:
dispatch_async + 串行队列(或并行队列)
如果是单个任务,使用串行队列或并行队列都可以
如果是多个任务,并且任务间有相互依赖关系,则使用串行队列,反之使用并行队列
多线程带来的麻烦是:
- 线程间同步问题
线程间同步要么通过阻塞方式,要么通过回调方式
主线程不能阻塞,所以主线程只能使用回调方式
GCD是的解决方法是:
- 如果主线程需要跟子线程同步,则在主线程中调用
void run_async(void (^block)(void), void (^callback)(void)){ // Do the work on the default concurrent queue dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(defaultQueue, ^{ block(); dispatch_async(dispatch_get_main_queue(), callback); });}
- 如果子线程需要等待主线程,则在子线程调用:
dispatch_sync(dispatch_get_main_queue(), ^{});
子线程可以阻塞,所以使用dispatch_sync
虽然上面提到线程的概念,但是在实际使用GCD时应该尽量避免用多线程的思维思考问题,而是用GCD的概念:
同步、异步、串行队列、并行队列、任务
并发队列
并发队列,顾名思义,队列中的任务是并发执行的
系统提供4个全局的并发队列
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
它们的唯一区别是优先级不同
串行队列
串行队列,顾名思义,队列中的任务是一个一个的顺序执行的,先进先执行
创建串行队列
dispatch_queue_t aQueue = dispatch_queue_create("com.example.MyQueue", NULL);
主队列
主队列是一个全局的串行队列
主队列中的任务是在主线程上串行运行的
dispatch_queue_t mainQueue = dispatch_get_main_queue();
内存管理(非arc)
dispatch queue 是基于引用计数的对象
dispatch_queue_t aQueue = dispatch_queue_create("com.example.MyQueue", NULL); // retainCount == 1dispatch_retain(aQueue); // retainCount == 2;dispatch_release(aQueue); // retainCount == 1;dispatch_release(aQueue); // retainCount == 0; 释放内存
全局队列不需要内存管理,因为是全局的
关联数据到串行队列
struct my_data { const char *name; int n;};void finalizer(void *data){ free(data);}int main(){ struct my_data *mydata = (struct my_data *) malloc(sizeof(struct my_data)); mydata->name = "aaa"; mydata->n = 2; dispatch_queue_t aQueue = dispatch_queue_create("aaa", NULL); dispatch_set_context(aQueue, mydata); dispatch_set_finalizer_f(aQueue, finalizer); dispatch_sync(aQueue, ^{ struct my_data *data = (struct my_data *) dispatch_get_context(aQueue); printf("%s\n", data->name); });}
添加任务到队列
int main(){ dispatch_queue_t myCustomQueue = dispatch_queue_create("com.example.MyCustomQueue", NULL); dispatch_async(myCustomQueue, ^{ printf("Do some work here, async.\n"); }); printf("The first block may or may not have run.\n"); dispatch_sync(myCustomQueue, ^{ printf("Do some more work here, sync.\n"); }); printf("Both blocks have completed.\n");}
dispatch_async
// 不阻塞,立刻返回 dispatch_sync
// 阻塞,直到任务完成才返回
异步回调
在子线程执行任务,在主线程执行回调
void run_on_sub_thread_async(void (^block)(void), void (^callback)(void)){ // Do the work on the default concurrent queue dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(defaultQueue, ^{ block(); dispatch_async(dispatch_get_main_queue(), callback); });}void test(){ run_on_sub_thread_async(^{ printf("do async\n"); }, ^{ printf("callback\n"); });}
子线程阻塞,主线程执行
在主线程执行一些ui操作后回到子线程继续
void run_on_main_thread_sync(dispatch_block_t block){ dispatch_sync(dispatch_get_main_queue(), block);}// 以下代码在子线程执行run_on_main_thread_sync(^{ // 执行UI操作});// 子线程阻塞,待 run_on_main_thread_sync 返回后,子线程继续执行// ...
并行for
void parallel_for(size_t n, void (^block)(size_t i)){ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(n, queue, block);}void test(){ parallel_for(1000, ^(size_t i) { for (int i = 0; i < 1000000; ++i) ; printf("%zu\n", i); });}
事件循环
本小节术语说明:
下文中说的 queue,线程的 queue,指的是数据结构 queue
下文中说的 gcd queue, 串行队列,并行队列等指的是 dispatch_queue_t
对于Cocoa应用程序,通过UIApplicationMain函数进入事件循环
如果是命令行程序,则可以通过dispatch_main进入事件循环
事件循环就是死循环,代码类似:
void dispatch_main(){ while (true) { while (queue_is_empty(queue)) wait(); // sleep block_t block = queue_dequeue(queue); if (block) block(); }}
dispatch_main
是在主线程中调用的,所以从 queue
里取出来的 block
也是在主线程中执行的
对于子线程也是一样的,每个子线程都有一个事件循环和一个对应的queue,事件循环不停的从队列中取出block来执行,如果队列为空就sleep,等待队列不为空时被唤醒,通常是用 条件变量 或 信号量 来进行同步
gcd管理一个线程池,线程池中的线程数量是有限的,但是gcd的串行队列和并行队列是可以创建任意多个的,这就需要将gcd的队列中的任务映射到有限的线程的队列中。
对于串行队列,gcd从线程池中挑一个线程,将串行队列中的任务加到到线程的队列中,由线程串行的执行队列中的任务。
对于并行队列,gcd从线程池中挑多个线程,将并行队列中的任务分散的加到多个线程的队列中,由多个线程并行执行
主线程的特殊之处在于
- 它不属于线程池
- 它的队列是全局的(只有一个)
- 它的队列必然是串行的(因为只有一个主线程)
对于gcd main queue,gcd的处理是和串行队列类似,只不过,gcd不是从线程池挑选线程,而是直接将gcd main queue中的任务加到主线程的队列中,由主线程串行执行。
主线程, 主线程的queue,gcd main queue 他们是一一对应的
实际测试得到的结论
子线程:是属于线程池里的一个线程
当前线程:即调用dispatch_sync
函数的线程,可能为主线程也可能为线程池里的一个子线程
这个结论跟之前的介绍并不冲突,gcd依然是将同步任务加到的子线程的队列中,只不过在调度到同步任务时唤醒dispatch_sync
,而不是在子线程中执行同步任务。 dispatch_sync
的实现可能是:
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block){ enqueue(queue, block); wait(); // 等待被唤醒 block(); // 当前线程执行}
挂起和恢复队列
dispatch_suspend dispatch_resume
挂起队列:就是从gcd queue对应的线程的queue里将任务移除
恢复队列:就是将gcd queue的任务加到线程的queue里
信号量
// Create the semaphore, specifying the initial pool sizedispatch_semaphore_t fd_sema = dispatch_semaphore_create(20);// Wait for a free file descriptordispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);fd = open("/etc/services", O_RDONLY);// Release the file descriptor when doneclose(fd);dispatch_semaphore_signal(fd_sema);
dispatch_semaphore_wait // 如果信号量大于0,则信号量减1且立刻返回,否则阻塞dispatch_semaphore_signal // 信号量加1且立刻返回
dispatch_group_t
将多个任务归为一组,如果有任务没完成,则dispatch_group_wait
阻塞, dispatch_group_wait
返回表示所有任务都完成了
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); // Add a task to the group dispatch_group_async(group, queue, ^{ for (int i = 0; i < 10000; ++i); printf("asyn1\n"); }); dispatch_group_async(group, queue, ^{ for (int i = 0; i < 100000; ++i); printf("asyn2\n"); }); dispatch_group_async(group, queue, ^{ for (int i = 0; i < 100; ++i); printf("asyn3\n"); }); dispatch_group_async(group, queue, ^{ for (int i = 0; i < 100000; ++i); printf("asyn4\n"); }); dispatch_group_async(group, queue, ^{ for (int i = 0; i < 1000; ++i); printf("asyn5\n"); }); dispatch_group_async(group, queue, ^{ for (int i = 0; i < 1000; ++i); printf("asyn6\n"); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); printf("done\n");
- gcd
- GCD
- GCD
- GCD
- GCD
- GCD
- GCD
- GCD
- GCD
- GCD
- GCD
- GCD
- gcd
- GCD
- GCD
- GCD
- GCD
- GCD
- HttpServletResponse输入输出流是否需要flush close
- spring-boot-admin基础搭建教程
- 前端基础(2):html语法(1)
- three.js 01-05 之使用 dat.GUI 库简化试验
- Ubuntu 16.10 文件服务器--samba的安装和配置
- GCD
- Android O 中CarAudioManager的调用流程分析
- Python异常处理
- Xcode 创建静态库
- 用HttpServletResponseWrappper 获取jsp 输出内容
- c++ std::function bind
- org.apache.zookeeper.KeeperException$InvalidACLException: KeeperErrorCode = InvalidACL for /f
- 【Android】AndroidStudio2.2.3 切换到AndroidStudio3.0之后问题处理记录
- 对一致性hash算法的理解