c语⾔中gcd的⽤法,(转)gcd简单使⽤和介绍
⼀、介绍
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");
// 发⽣死锁下⾯的代码不会执⾏
NSLog(@"middle");
456什么意思网络流行语});
// 发⽣死锁下⾯的代码不会执⾏,当然函数也不会返回,后果也最为严重
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更新
});
// 异步执⾏
// 主线程,UI更新
});
信号量的使⽤
也属于线程间通讯,下⾯的举例是经常⽤到的场景。在⽹络访问中,NSURLSession类都是异步的(了很久没有到同步的⽅法),⽽有时我们希望能够像NSURLConnection⼀样可以同步访问,即在⽹
络block调⽤完成之后做⼀些操作。那我们可以使⽤dispatch的信号量来解决:
/// ⽤于线程间通讯,下⾯是等待⼀个⽹络完成
- (void)dispatchSemaphore {
NSString *urlString = [@"www.baidu" 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];