GCD

概述

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 == 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从线程池中挑多个线程,将并行队列中的任务分散的加到多个线程的队列中,由多个线程并行执行

主线程的特殊之处在于

  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 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");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值