前言
其实在我写这边博客之前,也在查阅不好资料,但是发现网上很多人说的内容总结,其实并不正确,导致自己也踩了不少坑,所以才想着重新总结一下,给自己做个参考,也当是复习一下,当然我也可能有不对的地方,希望可以得到改正
知识点梳理
基本概念简述
-
1.同步函数dispatch_sync 必须等待当前语句执行完毕,才会进行下一条,在当前执行block任务
-
2.异步函数dispatch_async 不用等当前语句执行完毕,就可以执行下一条语句,会开启线程执行block,异步多线程的代名词,主队列例外
还原最基础的写法,很重要
- (void)syncTest{
// 把任务添加到队列 --> 函数
// 任务 _t ref c对象
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
//串行队列
dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", NULL);
// 函数
dispatch_async(queue, block);
}
上面就是把同步函数步骤进行拆分,也是为了便于理解
而接下来会以实际例子去测试再结合理论知识去总结概括,为了方便大家查阅,我全部是都代码形式复制过来,没有截图,也方便大家更快的找到自己想看的题目类型,当然也希望有大佬给出意见和新的题型让我补充🙏🙏🙏🙏
建议大家先分析之后在看结果,我验证的时候翻车不少😂😂😂😂
例题1:
/**
主队列同步
不会开线程
*/
- (void)mainSyncTest{
NSLog(@"0");
// 等
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"1");
});
NSLog(@"2");
}
解析:主队列 + 同步函数(sync函数) 产生死锁 1 2互相等待
例题2:
/**
串行同步队列 : FIFO: 先进先出
*/
- (void)serialSyncTest{
//1:创建串行队列
dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<20; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
}
解析:串行队列 先进先出 虽然连续添加了20次任务 但是必须依次执行,所以结果是依次打印1-19
例题3:
/**
主队列异步
不会开线程 顺序
*/
- (void)mainAsyncTest{
NSLog(@"0");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
NSLog(@"2---%@",[NSThread currentThread]);
}
解析:主队列 串行 + 异步 还是依次执行 但是 1 必须等 2执行
例题4:
/**
全局异步
全局队列:一个并发队列
*/
- (void)globalAsyncTest{
for (int i = 0; i<20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
解析:全局队列 并发 + 异步 多线程同步执行 执行次数还是20次 只不过不同线程执行次数随机
例题5:
/**
异步并发: 有了异步函数不一定开辟线程
*/
- (void)concurrentAsyncTest{
//1:创建并发队列
dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<20; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
解析:结果和上面差不多 会另开线程,但是开几条线程不定,看线程池调度状况
例题6:
/**
全局同步
全局队列:一个并发队列
*/
- (void)globalSyncTest{
for (int i = 0; i<20; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
解析:全局队列 并发 + 同步 依次执行 NSLog 最后执行,不开辟新线程
例题7:
/**
同步并发 : 堵塞 同步锁 队列 : resume supend 线程 操作, 队列挂起 任务能否执行
*/
- (void)concurrentSyncTest{
//1:创建并发队列
dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<20; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
解析:上面全局队列结果一样
例题8:
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
解析:并发队列 异步+同步 结果:1 5和(234)并发 也就是说234顺序不会变 而5可能在他们之间任意位置出现 大概率15234
例题9:
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 耗时
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
解析:并发队列 异步+异步 结果:1 首位 2先于34,34位置不定 而5可在234任意位置 大概率是15243 一般情况下主线程肯定执行更稳定 另开线程在执行任务会稍微慢些 虽然是并发,但是本质CPU快速切换的过程 所以才说大概率是15243
例题10:
/**
串行异步队列
*/
- (void)serialAsyncTest{
//1:创建串行队列
dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<20; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
解析:其实和第一个例子相似,主队列也是串行队列,所以串行队列异步,不开线程,并且异步函数要在当前线程函数任务执行完毕在执行,所以NSLog永远先执行
例题11:
/**
可变数组 线程不安全 解决办法
*/
- (void)demo3{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
//答案不确定
//dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT); 答案1000
for (int i = 0; i<1000; i++) {
dispatch_async(concurrentQueue, ^{
NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
dispatch_barrier_async(concurrentQueue , ^{
[self.mArray addObject:image];
});
});
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"数组的个数:%zd",self.mArray.count);
}
解析:栅栏函数 配合并发队列可以对异步并发起到拦截作用,确保在数组插入数据过程的线程安全,依次插入数据,但是栅栏函数只能对自定义队列有效,系统队列不行,因此答案不确定,如果是自定义并发队列,就是数组个数就是1000
同时这里也体现了栅栏函数使用的局限性,它只能对同一个队列进行拦截操作,如果是不同队列就行不通了,比如在我们使用一些第三方库的方法时,你也不知道他们底层是使用的什么队列,自然而然没办法使用栅栏函数处理
例题12:
- (void)demo2{
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue1 = dispatch_queue_create("kc", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
// 这里是可以的额!
/* 1.异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"2");
});
/* 2. 栅栏函数 */ // - dispatch_barrier_sync
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"----%@-----",[NSThread currentThread]);
});
/* 3. 异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"3");
});
// 4
NSLog(@"4");
}
解析:栅栏函数同步+并发队列 依然可以起到同步的作用,但是会阻塞当前线程,也就是说3 要等 1和2执行完毕,且栅栏函数的任务也执行完毕 ,才能执行,同时4也必须等栅栏函数执行完毕,所以12随机 先于 34随机
例题13:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
sleep(2);
NSLog(@"执行任务1");
NSLog(@"任务1完成");
});
//任务2
dispatch_async(queue, ^{
sleep(2);
NSLog(@"执行任务2");
NSLog(@"任务2完成");
dispatch_semaphore_signal(sem); // 发信号
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
NSLog(@"任务3完成");
dispatch_semaphore_signal(sem);
});
解析:初始信号量0,全局并发队列异步函数,按理说并发执行,但是初始信号量0,所以任务1和任务3都在等待,任务2执行完毕后,发送信号,先等待的先接受,所以大概率执行任务1,任务3不执行
例题14:
dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
//任务1
dispatch_async(queue, ^{
sleep(10);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
NSLog(@"执行任务1");
NSLog(@"任务1完成");
});
//任务2
dispatch_async(queue, ^{
NSLog(@"执行任务2");
NSLog(@"任务2完成");
dispatch_semaphore_signal(sem); // 发信号
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
NSLog(@"任务3完成");
dispatch_semaphore_signal(sem);
});
//任务4
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务4");
NSLog(@"任务4完成");
dispatch_semaphore_signal(sem);
});
解析:首先任务2先执行毫无疑问,任务3和4会先于1,因为1卡线程10秒,等待会比3和4晚,而3和4执行后会在发送一下信号,而任务1呢,因为信号的机制,尽管等待10秒,但只要执行等待信号函数,就能接收到信号量现在还是1,所以还是会执行
例题15:
dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue1 = dispatch_queue_create("ycx", NULL);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
sleep(2);
NSLog(@"执行任务1");
NSLog(@"任务1完成");
});
//任务2
dispatch_async(queue1, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"执行任务2");
NSLog(@"任务2完成");
});
//任务3
dispatch_async(queue1, ^{
sleep(2);
NSLog(@"执行任务3");
NSLog(@"任务3完成");
dispatch_semaphore_signal(sem);
});
//任务4
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"执行任务4");
NSLog(@"任务4完成");
dispatch_semaphore_signal(sem);
});
解析:首先要看清楚queue和queue1两个自定义队列,一个并发,一个串行,因为任务2和3是在串行队列执行,尽管是异步函数,但是没有另开线程,而任务2又有信号等待造成线程阻塞,无法执行完毕,那么任务3也就没办法执行,也就没发发送信号,所以没有任务能够执行
例题16:
实现一个场景:图片异步加载,确保全部加载完成后,再使用,保证数据安全
第一个方案:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
__weak typeof(self) weakSelf = self;
dispatch_group_async(group, queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
UIImage *image1 = [UIImage imageWithData:data1];
dispatch_barrier_async(queue, ^{
[strongSelf.mArray addObject:image1];
NSLog(@"showTime----111111111");
});
});
dispatch_group_async(group, queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
UIImage *image2 = [UIImage imageWithData:data2];
dispatch_barrier_async(queue, ^{
[strongSelf.mArray addObject:image2];
NSLog(@"showTime----222222222");
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"数组个数:%ld",self.mArray.count);
});
解析:常见调度组的使用场景,确保图片异步加载完毕,再统一处理,使用栅栏函数确保数组写入安全
第二个方案:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue1 = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
__weak typeof(self) weakSelf = self;
dispatch_group_async(group, queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
UIImage *image1 = [UIImage imageWithData:data1];
dispatch_barrier_sync(queue1, ^{
[strongSelf.mArray addObject:image1];
NSLog(@"showTime----111111111");
});
});
dispatch_group_async(group, queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
UIImage *image2 = [UIImage imageWithData:data2];
dispatch_barrier_sync(queue1, ^{
[strongSelf.mArray addObject:image2];
NSLog(@"showTime----222222222");
});
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"数组个数:%ld",self.mArray.count);
解析:dispatch_group_wait函数会等待前面group里面的任务执行完毕在执行,后面的任务,会阻塞当前线程,从而达到统一处理,同时使用dispatch_barrier_sync和并发队列,确保数据写入的安全,切记使用dispatch_barrier_sync是一定要另外再创建一个队列,如果和调度组使用同一个队列,就会造成死锁,互相等待执行,如下
例题17:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
__weak typeof(self) weakSelf = self;
dispatch_group_async(group, queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
UIImage *image1 = [UIImage imageWithData:data1];
dispatch_barrier_sync(queue, ^{
[strongSelf.mArray addObject:image1];
NSLog(@"showTime----111111111");
});
NSLog(@"showTime----333333");
});
dispatch_group_async(group, queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
UIImage *image2 = [UIImage imageWithData:data2];
dispatch_barrier_sync(queue, ^{
[strongSelf.mArray addObject:image2];
NSLog(@"showTime----222222222");
});
NSLog(@"showTime----444444");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"数组个数:%ld",self.mArray.count);
解析:死锁在于 dispatch_barrier_sync 会堵塞当前线程执行,而栅栏函数又必须等待前面加入队列queue的任务执行完毕才可以执行,最终就会在queue队列中的某个线程死锁,group调度组任务永远无法执行完毕,dispatch_group_wait就一直等待
例题18:
接着例题16的第三个方案
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
__weak typeof(self) weakSelf = self;
dispatch_group_enter(group);
dispatch_async(queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
UIImage *image1 = [UIImage imageWithData:data1];
dispatch_barrier_async(queue, ^{
[strongSelf.mArray addObject:image1];
NSLog(@"showTime----111111111");
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
UIImage *image2 = [UIImage imageWithData:data2];
dispatch_barrier_async(queue, ^{
[strongSelf.mArray addObject:image2];
NSLog(@"showTime----222222222");
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"数组个数:%ld",self.mArray.count);
});
解析:dispatch_group_enter和dispatch_group_leave 进组出组配合使用,有点像信号量,也是必须进出组刚好对等,才可以执行dispatch_group_notify里面的任务
如果后续发现一些有意思的题目,也会继续收录补充,主要也是为了加深对于多线程的理解和运用
今天的分享就到次为止,下一篇会着重分析GCD的底层源码