概述
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 == 1
dispatch_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 size
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(20);
// Wait for a free file descriptor
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
fd = open("/etc/services", O_RDONLY);
// Release the file descriptor when done
close(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");