GCD

来源:互联网 发布:后拱辰享雪面霜知乎 编辑:程序博客网 时间:2024/05/22 00:27

概述

GCD是并发编程的发展方向,对于应用程序来说,GCD消除了线程的概念,甚至不需要考虑太多并发的问题,GCD的模型是基于任务的。

多线程主要提供两大好处:

  1. 避免阻塞主线程(通过开一个子线程来实现)
  2. 提高程序整体性能(通过开多个子线程来实现)

而GCD是:
通过下面的方法来实现:

dispatch_async + 串行队列(或并行队列)
如果是单个任务,使用串行队列或并行队列都可以
如果是多个任务,并且任务间有相互依赖关系,则使用串行队列,反之使用并行队列

多线程带来的麻烦是:

  1. 线程间同步问题

线程间同步要么通过阻塞方式,要么通过回调方式
主线程不能阻塞,所以主线程只能使用回调方式

GCD是的解决方法是:

  1. 如果主线程需要跟子线程同步,则在主线程中调用
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);    });}
  1. 如果子线程需要等待主线程,则在子线程调用:
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从线程池中挑多个线程,将并行队列中的任务分散的加到多个线程的队列中,由多个线程并行执行

主线程的特殊之处在于

  1. 它不属于线程池
  2. 它的队列是全局的(只有一个)
  3. 它的队列必然是串行的(因为只有一个主线程)

对于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");
原创粉丝点击