c语言中gcd的用法,(转)gcd简单使用和介绍

GCD(Grand Central Dispatch)是苹果的多线程解决方案,提供串行队列和并行队列,通过任务(block)添加到队列执行。同步执行在当前线程进行,异步执行可能开启新线程。死锁通常发生在同步执行主队列时。调度组(DispatchGroup)用于监控多个任务完成,常用于线程间通讯和网络请求的同步。使用时注意避免主队列死锁,并合理利用全局队列和主队列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、介绍

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

二、任务和队列

任务:要执行的操作或方法函数

队列:存放任务的集合

而我们要做的就是将任务添加到队列然后执行,GCD会自动将队列中的任务按先进先出的方式取出并交给对应线程执行。注意任务的取出是按照先进先出的方式,这也是队列的特性,但是取出后的执行顺序则不一定

1、任务

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

2、队列queue

串行队列:同步执行,在当前线程执行;

并行队列:可由多个线程异步执行,但任务的取出还是按照先进先出原则

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

/** 下面代码为创建一个串行队列,也是实际开发中用的最多的

参数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, 0);

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

// 获取主队列

serialQ = dispatch_get_main_queue();

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

3、执行方式

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

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

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

/** 同步执行

第一个参数:执行任务的队列:串行、并行、全局、主队列

第二个参数:block任务

*/

void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

// 异步执行

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

另外还有两个方法,实际开发中用的并不是太多

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。

- (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(3);

NSLog(@"task2,%@", [NSThread currentThread]);

});

dispatch_async(BCqueue, ^{

sleep(1);

NSLog(@"task3,%@", [NSThread currentThread]);

});

// 2.2 添加barrier

dispatch_barrier_async(BCqueue, ^{

NSLog(@"barrier");

});

// 2.3 添加任务456

dispatch_async(BCqueue, ^{

sleep(1);

NSLog(@"task4,%@", [NSThread currentThread]);

});

dispatch_async(BCqueue, ^{

NSLog(@"task5,%@", [NSThread currentThread]);

});

dispatch_async(BCqueue, ^{

NSLog(@"task6,%@", [NSThread currentThread]);

});

}

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

2018-08-11 11:32:38.053836+0800 Demo[84735:3560810] task1,{number = 3, name = (null)}

2018-08-11 11:32:39.059182+0800 Demo[84735:3560811] task3,{number = 4, name = (null)}

2018-08-11 11:32:41.057478+0800 Demo[84735:3560808] task2,{number = 5, name = (null)}

2018-08-11 11:32:41.057894+0800 Demo[84735:3560808] barrier

2018-08-11 11:32:41.058375+0800 Demo[84735:3560810] task6,{number = 3, name = (null)}

2018-08-11 11:32:41.058375+0800 Demo[84735:3560811] task5,{number = 4, name = (null)}

2018-08-11 11:32:42.061265+0800 Demo[84735:3560808] task4,{number = 5, name = (null)}

三、几种类型

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

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

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

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

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

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

四、死锁举例

主队列死锁:

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

- (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");

}

五、常用举例

线程间通讯

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

// 同步执行,会阻塞指导下面block中的代码执行完毕

dispatch_sync(dispatch_get_main_queue(), ^{

// 主线程,UI更新

});

// 异步执行

dispatch_async(dispatch_get_main_queue(), ^{

// 主线程,UI更新

});

信号量的使用

也属于线程间通讯,下面的举例是经常用到的场景。在网络访问中,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:30];

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

[[[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最好不要在主线程调用

全局队列,实现并发:

dispatch_async(dispatch_get_global_queue(0, 0), ^{

// 要执行的代码

});

六、Dispatch Group调度组

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

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

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

[self groupTest1];

}

// 任务1完成之后执行任务2。

- (void)groupTest1 {

// 创建一个组

dispatch_group_t group = dispatch_group_create();

NSLog(@"开始执行");

dispatch_async(dispatch_get_global_queue(0, 0), ^{

dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{

// 任务1

// 等待1s一段时间在执行

[NSThread sleepForTimeInterval:1];

NSLog(@"task1 running in %@",[NSThread currentThread]);

[NSThread sleepForTimeInterval:2];

});

dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{

// 任务2

NSLog(@"task2 running in %@",[NSThread currentThread]);

});

});

}

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

2018-08-11 11:53:57.563670+0800 Demo[85123:3575340] 开始执行

2018-08-11 11:53:58.566972+0800 Demo[85123:3575416] task1 running in {number = 3, name = (null)}

2018-08-11 11:54:00.568308+0800 Demo[85123:3575416] task2 running in {number = 3, 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(0, 0), ^{

dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{

// 关联任务1

NSLog(@"task1 running in %@",[NSThread currentThread]);

});

dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{

// 关联任务2

NSLog(@"task2 running in %@",[NSThread currentThread]);

});

dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{

// 关联任务3

NSLog(@"task3 running in %@",[NSThread currentThread]);

});

dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{

// 关联任务4

// 等待1秒

[NSThread sleepForTimeInterval:1];

NSLog(@"task4 running in %@",[NSThread currentThread]);

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

// 回到主线程执行

NSLog(@"mainTask running in %@",[NSThread currentThread]);

});

});

}

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

2018-08-11 11:54:53.470182+0800 Demo[85165:3576500] 开始执行

2018-08-11 11:54:53.470472+0800 Demo[85165:3576556] task1 running in {number = 3, name = (null)}

2018-08-11 11:54:53.470651+0800 Demo[85165:3576558] task3 running in {number = 4, name = (null)}

2018-08-11 11:54:54.474466+0800 Demo[85165:3576560] task2 running in {number = 5, name = (null)}

2018-08-11 11:54:56.474165+0800 Demo[85165:3576559] task4 running in {number = 6, name = (null)}

2018-08-11 11:54:57.474927+0800 Demo[85165:3576500] mainTask running in {number = 1, name = main}

关于Dispatch对象内存管理问题

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

如果是ARC环境,我们无需管理,会像对待OC对象一样自动内存管理。

如果是MRC环境,不是使用retain/release,而是使用dispatch_retain/dispatch_release来管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值