最近颇花了一番功夫把多线程GCD人的一些用法总结出来,一来帮自己巩固一下知识、二来希望能帮到对这一块还迷茫的同学。
GCD是Grand Central Dispatch这三个英文单词的缩写,它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是c语言,不过由于使用了Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。
一、任务和队列
在GCD中,加入了两个非常重要的概念:任务和队列。
任务:即操作,你想要干什么,说白了就是一段代码,在GCD中就是一个Block,所以添加任务十分方便。任务有两种执行方式:同步执行和异步执行,他们之间的区别是是否会创建新的线程。
同步(sync)和异步(async)的主要区别在于会不会阻塞当前线程,直到Block中的任务执行完毕!
如果是同步(sync)操作,它会阻塞当前线程并等待Block中的任务执行完毕,然后当前线程才会继续往下运行。
如果是异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。
队列:用于存放任务。一共有两种队列,串行队列和并行队列。
串行队列:队列中的任务,GCD会FIFO(先进先出)地取出来一个,执行一个,然后取下一个,这样一个一个的执行。
并行队列:队列中的任务,GCD也会FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
1.0 创建和获得队列
1.1 主队列:这是一个特殊的串行队列。什么是主队列,大家都知道吧,它用于刷新UI,任何需要刷新UI的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。
dispatch_queue_t queue = dispatch_get_main_queue();
1.2 自己创建的队列:其中第一个参数是标识符,用于DEBUG的时候标识唯一的队列,可以为空。大家可以看xcode的文档查看参数意义。第二个参数用来表示创建的队列是串行的还是并行的,传入DISPATCH_QUEUE_SERIAL或NULL表示创建串行队列。传入DISPATCH_QUEUE_CONCURRENT表示创建并行队列。
串行队列
dispatch_queue_t queue1 = dispatch_queue_create("tk.bourne.testQueue", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
并行队列
dispatch_queue_t queue3 = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
1.3 全局并行队列:只要是并行任务一般都加入到这个队列。这是系统提供的一个并发队列。
第一个参数线程优先级
第二个参数0 :占位符、目前没有意义
dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1.4 Global Dispatch Queue(高优先级)的获取方法
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
1.5 Global Dispatch Queue(默认优先级)的获取方法
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1.6 Global Dispatch Queue(低优先级)的获取方法
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
1.7 Global Dispatch Queue(后台优先级)的获取方法
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
2.0 创建任务
同步任务:会阻塞当前线程(SYNC)
dispatch_sync(queue1, ^{
NSLog(@"%@", [NSThread currentThread]);
});
异步任务:不会阻塞当前线程(ASYNC)
dispatch_async(queue3, ^{
NSLog(@"%@", [NSThread currentThread]);
});
二、队列组
队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。
1.创建队列组
dispatch_group_t group = dispatch_group_create();
2.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
3.多次使用队列组的方法执行任务,只有异步方法
3.1.执行3次循环
dispatch_group_async(group, queue, ^{
for(NSIntegeri =0; i <3; i++) {
NSLog(@"group-01 - %@", [NSThreadcurrentThread]);
}
});
3.2.主队列执行8次循环
dispatch_group_async(group,dispatch_get_main_queue(), ^{
for(NSIntegeri =0; i <8; i++) {
NSLog(@"group-02 - %@", [NSThreadcurrentThread]);
}
});
3.3.执行5次循环
dispatch_group_async(group, queue, ^{
for(NSIntegeri =0; i <5; i++) {
NSLog(@"group-03 - %@", [NSThreadcurrentThread]);
}
});
4.都完成后会自动通知
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
NSLog(@"完成- %@", [NSThreadcurrentThread]);
});
三、dispatch_barrier_sync
为了高效率地进行访问,读取处理追加到Concurrent Dispathc Queue中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理结束之前,读取处理不可执行)。
虽然利用Dispatch Group和dispatch_set_target_queue函数也可实现,但是源码会很复杂。GCD为我们提供了高效的dispatch_barrier_sync函数。该函数同dispatch_queue_create函数生成的Concurrent Dispathc Queue一起使用。
dispatch_queue_tqueue =dispatch_queue_create("id=929",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThreadsleepForTimeInterval:6];
NSLog(@"reading01");
});
dispatch_async(queue, ^{
[NSThreadsleepForTimeInterval:3];
NSLog(@"reading02");
});
dispatch_async(queue, ^{
[NSThreadsleepForTimeInterval:2];
NSLog(@"reading03");
});
dispatch_async(queue, ^{
[NSThreadsleepForTimeInterval:9];
NSLog(@"reading04");
});
#warning dispatch_barrier_async会监控queue直到先加入队列的任务执行完才会执行自己加入的任务,并且会阻塞queue直到自己block中的任务执行完才会让后续任务执行。dispatch_barrier_async函数本身是异步的不会阻塞当前线程
dispatch_barrier_async(queue, ^{
[NSThreadsleepForTimeInterval:5];
NSLog(@"writing");
});
NSLog(@"走到dispatch_barrier_async函数下面了");
dispatch_async(queue, ^{
NSLog(@"reading05");
});
dispatch_async(queue, ^{
NSLog(@"reading06");
});
dispatch_async(queue, ^{
NSLog(@"reading07");
});
四、dispatch_set_target_queue
/**
dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。
*/
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("id=19", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
//变更优先级的函数dispatch_set_target_queue
/**
参数一:要变更优先级的Dispatch Queue
参数二:目标优先级Dispatch Queue,与目标Dispatch Queue有相同的执行优先级
*/
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
五、dispatch_after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
/**
参数一:指定时间用的 dispatch_time_t 类型值
参数二:在哪个队列执行
参数三:要执行的任务
*/
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds.");
});
#warning dispatch_after函数并不是指定延迟后执行block里的任务,而是指定时间后把任务加进队列
六、dispatch_apply
/**
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。
*/
/**
参数一: 重复次数
参数二: 追加对象的Dispatch Queue
参数三: 追加的处理
与目前为止所出现的例子不同,第三个参数的Block为带有Block的参数。这是为了按第一个参数重复追加Block并区分各个Block而使用。
*/
// dispatch_apply(10, queue, ^(size_t index) {
// NSLog(@"%zu",index);
// });
//#warning dispatch_apply会阻塞当前线程直到queue中新添加的10次任务执行结束才会取消阻塞。要想10次任务依次执行可以把queue换成一个串行队列
// NSLog(@"done");
/* 例如要对NSArray类对象的所有元素执行处理时,不必一个一个编写for循环部分。*/
// NSArray *array = [NSArray arrayWithObjects:@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11, nil];
// dispatch_apply([array count], queue, ^(size_t index) {
// NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
// });
/* 由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数 */
NSArray *array = [NSArray arrayWithObjects:@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11, nil];
dispatch_async(queue, ^{
//Global Dispatch Queue 等待dispatch_appply函数中全部处理执行结束
dispatch_apply([array count], queue, ^(size_t index) {
//并列处理包含在NSArray对象的全部对象
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
//dispatch_apply函数中的处理全部执行结束
/* 在Main Dispatch Queue中非同步执行 */
dispatch_async(dispatch_get_main_queue(), ^{
/*
在Main Dispatch Queue中执行处理
用户界面更新等
*/
NSLog(@"done");
});
});
七、dispatch_suspend / dispatch_resume
/**
当追加大量处理到Dispatch Queue时,在追加处理过程中,有时希望不执行已经追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响。
在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再恢复。
*/
dispatch_queue_t queue1 = dispatch_queue_create("id = 88", DISPATCH_QUEUE_CONCURRENT);
//dispatch_suspend函数挂起指定的Dispatch Queue
dispatch_suspend(queue1);
//dispatch_resume函数恢复指定的Dispatch Queue
dispatch_resume(queue1);
#warning 这些函数对已经执行的处理没有影响。挂起后,追加到Dispatch Queue中尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。
八、Dispatch Semaphore
/**
当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可避免这类问题,但有必要进行更细粒度的排他控制。
*/
#pragma mark - 出错的代码
// NSMutableArray *array = [[NSMutableArray alloc]init];
// for (int i = 0; i < 100000; i++) {
// dispatch_async(queue, ^{
// [array addObject:[NSNumber numberWithInt:i]];
// });
// }
/*因为该源代码使用Global Dispatch Queue更新NSMutableArray类对象,所在执行后由于内存错误导致应用程序结束的概率很高。此时应使用Dispatch Semaphore。
Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0时等待,计数为1或大于1时,减1而不等待。
*/
// //设置参数为1
// dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
// //设置超是等待,即最长等待时间
// dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
// /**
// 参数一: dispatch_semaphore_t对象
// 参数二: 最长等待时间,与dispatch_group_wait中的超时等待是一样的
// */
// long result = dispatch_semaphore_wait(semaphore, time);
// if (result == 0) {
//
// }else{
//
// }
/** 用Dispatch Semaphore改写出错的代码
生成Dispatch Semaphore
Dispatch Semaphore的计数初始值设定为1
保证可访问NSMutableArray类对象的线程同时只能有一个。
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc]init];
for (int i = 0; i < 100000; i++) {
dispatch_async(queue, ^{
/**
等待Dispatch Semaphore
一直等待,直到Dispatch Semaphore的计数值达到大于等于1
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
/**
由于Dispatch Semaphore的计数值达到或大于等于1
所以将Dispatch Semaphore的计数值减去1
dispatch_semaphore_wait函数执行返回
即执行到此时的Dispatch Semaphore的计数恒为0
由于可访问NSMutableArray类对象的线程只有1个
因此可安全地进行更新
*/
[array addObject:[NSNumber numberWithInt:i]];
NSLog(@"%d",i);
/**
排他控制处理结束,
所以通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1
如果有通过dispatch_semaphore_wait函数
等待Dispatch Semaphore的计数值增加的线程,
就由最先等待的线程执行。
*/
dispatch_semaphore_signal(semaphore);
});
}
九、dispatch_once
/* dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。 */
static int initialized = NO;
if (initialized == NO) {
//初始化
initialized = YES;
}
#warning 上面的源代码在大多数情况下也是安全的,但是在多核CPU中,在正在更新表示是否初始化的标志变量时读取,就有可能多次执行初始化处理。而dispatch_once函数就没有这个问题,通过dispatch_once函数生成的代码即使在多线程环境下执行,也可保证百分百安全。
/* 上面的代码可以简化 */
static dispatch_once_t pred;
dispatch_once(&pred, ^{
//初始化
});
-----------------------我是无趣的分割线---------------------
上面这么枯躁的东西都能坚持看完,到此已经证明你有编程的资质!祝你编程之路一路顺风!!!