iOS GCD线程同步

题外话
原本打算每周至少整理两篇博客的,看来真的是高估自己了...
在此给自己立个FLAG,一周至少一篇,恳请监督(大哭...)

言归正传
本篇博客主要讲解GCD的线程同步应用,对于什么是GCD的基础问题,不太明白的还是建议大家去看看基础,强烈推荐iOS多线程--彻底学会多线程之『GCD』(写的真的很棒哦)

线程同步

  • 线程组group
  • 信号量semaphore
  • 栅栏barrier

一. 线程组group

可以使用dispatch_group_async函数将多个任务关联到一个线程组dispatch_group和相应的队列queue中,dispatch_group会并发地同时执行这些任务。
而且dispatch_group可以用来阻塞一个线程,直到dispatch_group关联的所有的任务完成执行。有时候必须等待任务完成的结果,然后才能继续后面的处理。

dispatch_group同步应用

首先执行任务1、任务2、任务3,执行完后,接着执行任务0

/**
* 创建一个队列
* 参数1:队列名(C语言写法)
* 参数2:队列类型(串行/并行)
* DISPATCH_QUEUE_SERIAL串行
* DISPATCH_QUEUE_CONCURRENT 并行
*/
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    
// 创建一个调度组group(线程组)
dispatch_group_t group = dispatch_group_create();
    
// 向线程组中添加多个任务
dispatch_group_async(group, queue, ^{
  NSLog(@"mission--1");
});
    
NSLog(@"===aaa===");
    
dispatch_group_async(group, queue, ^{
  NSLog(@"mission--2");
});
    
dispatch_group_notify(group, queue, ^{
  NSLog(@"dispatch_group_notify:mission--0");
});
    
dispatch_group_async(group, queue, ^{
  NSLog(@"mission--3");
});

运行结果

dispatch_group_01.png

分析
1.dispatch_group会等和它关联的所有的dispatch_queue上的任务都执行完毕才会发出同步信号,即dispathc_group_notify的代码块block才会被执行。
2.dispathc_group_notify的作用:在group中的其他操作全部完成后,再操作自己的内容,所以我们会看到上面任务1、任务2、任务3执行之后,才执行任务0。

问题升级

如使用AFNetworking添加异步任务时,上述方法显然无效。
因为网络请求需要时间,而线程的执行并不会等待请求完成后才真正算作完成,而是只负责将请求发出去,线程就认为自己的任务算完成了,当三个请求都发送出去,就会执行dispathc_group_notify中的内容,但请求结果返回的时间是不一定的,也就导致界面都刷新了,请求才返回,这就是无效的。
因此需要使用dispatch_group_enter、dispatch_group_leave(需要成对出现)

dispatch_group_t group = dispatch_group_create();
    
dispatch_group_enter(group);
[self demoBlock:^(NSString *str) {
  NSLog(@"mission--1");
  dispatch_group_leave(group);
}];
    
dispatch_group_enter(group);
[self demoBlock:^(NSString *str) {
  NSLog(@"mission--2");
  dispatch_group_leave(group);
}];
    
dispatch_group_enter(group);
[self demoBlock:^(NSString *str) {
  NSLog(@"mission--3");
  dispatch_group_leave(group);
}];
    
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
dispatch_group_notify(group, mainQueue, ^{
  NSLog(@"mission--0");
});

为此我们还需要简单的写一个block回调
.m文件中添加扩展,写入:

- (void)demoBlock:(void(^)(NSString *str))block;

实现方法:

- (void)demoBlock:(void(^)(NSString *str))block {
    
    block(@"demoBlock");
}

运行结果

dispatch_group_02.png

分析
1.和dispatch_async相比,当我们调用n次dispatch_group_enter后再调用n次dispatch_group_leave时,dispatch_group_notify和dispatch_group_wait会收到同步信号(这里没有给出dispatch_group_wait的具体应用,需要了解的朋友可以自行查阅相关内容)
2.应用场景:处理异步任务的同步,当异步任务开始前调用dispatch_group_enter,异步任务结束后调用dispatch_group_leave。
适用于后台批量下载,结束后主线程统一刷新UI。

二. 信号量semaphore

首先,我们必须了解什么是信号量,在这里给大家简单的介绍一下(小白可以去查一些相关资料)


信号量.png

以下是简单的个人理解(伪代码)

 信号量
 信号量 初始值 v = 3
 
 a come
 if v < 0 wait
 else v = v - 1
 // v = 2; state: a in
 
 b come
 if v < 0 wait
 else v = v - 1
 // v = 1; state: b in
 
 c come
 if v < 0 wait
 else v = v - 1
 // v = 0; state: c in
 
 d come
 if v < 0 wait
 else v = v - 1
 // state: d wait
 
 e come
 if v < 0 wait
 else v = v - 1
 // state: e wait
 
 f come
 if v < 0 wait
 else v = v - 1
 // state: f wait
 
 b out // v = v + 1 = 1
 if v < 0 wait
 else v = v - 1
 // v = 0; state: f in
 
 ...

方法

  • dispatch_semaphore_create:创建一个信号量(semaphore)
  • dispatch_semaphore_signal:信号通知,即让信号量+1
  • dispatch_semaphore_wait:等待,直到信号量大于0时,即可操作,同时将信号量-1

还是上面的例子

/**
  * 获取全局队列
  * 参数1:选择的是哪个优先级的全局队列
  * 参数2:作为保留字段备用(一般为0)
*/
 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
 // 创建线程组
 dispatch_group_t group = dispatch_group_create();
    
 // 向线程组中添加任务
dispatch_group_async(group, globalQueue, ^{
  // 创建一个信号量 初始值为0
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  [self demoBlock:^(NSString *str) {
    NSLog(@"mission--1");
    // 执行了block之后 将信号量+1
    dispatch_semaphore_signal(semaphore);
  }];
  // 在成功执行block之前,信号量必须等待
  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
    
NSLog(@"===aaa===");
    
dispatch_group_async(group, globalQueue, ^{
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  [self demoBlock:^(NSString *str) {
    NSLog(@"mission--2");
    dispatch_semaphore_signal(semaphore);
    }];
  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
    
dispatch_group_async(group, globalQueue, ^{
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  [self demoBlock:^(NSString *str) {
    NSLog(@"mission--3");
    dispatch_semaphore_signal(semaphore);
  }];
  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
    
dispatch_group_notify(group, globalQueue, ^{
  NSLog(@"dispatch_group_notify:mission--0");
});

运行结果

dispatch_semaphore.png

分析
1.在每个请求开始之前,我们创建一个信号量,初始为0,在请求操作之后,我们设一个dispatch_semaphore_wait,在请求到结果之后,再将信号量+1,即执行dispatch_semaphore_signal。
2.这样做的目的是保证在请求结果没有返回之前,一直让线程等待在那里,这样一个线程的任务一直在等待,就不会算作完成,notify的内容也就不会执行了,直到每个请求的结果都返回了,线程任务才能够结束,这时候notify也才能够执行。

三. 栅栏barrier

直接先上个例子:首先执行任务1、任务2、任务3,执行完后,接着执行任务0,任务0执行之后,再执行任务4、任务5、任务6


barrier.png

分别使用dispatch_barrier_sync函数和dispatch_barrier_async函数来完成

dispatch_barrier_sync

dispatch_queue_t queue = dispatch_queue_create("mickyQueue", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
  NSLog(@"mission--1");
});
    
dispatch_async(queue, ^{
  NSLog(@"mission--2");
});
    
dispatch_async(queue, ^{
  NSLog(@"mission--3");
});
    
dispatch_barrier_sync(queue, ^{
  for (int i = 0; i < 10000; i++) {
    if (700 == i) {
      NSLog(@"point1");
    } else if (800 == i) {
      NSLog(@"point2");
    } else if (900 == i) {
      NSLog(@"point3");
    }
  }
  NSLog(@"barrier");
});
    
NSLog(@"===aaa===");
    
dispatch_async(queue, ^{
  NSLog(@"mission--4");
});
    
NSLog(@"===bbb===");
    
dispatch_async(queue, ^{
  NSLog(@"mission--5");
});
    
dispatch_async(queue, ^{
  NSLog(@"mission--6");
});
    

运行结果

dispatch_barrier_sync.png

dispatch_barrier_async

dispatch_queue_t queue = dispatch_queue_create("mickyQueue", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
  NSLog(@"mission--1");
});
    
dispatch_async(queue, ^{
  NSLog(@"mission--2");
});
    
dispatch_async(queue, ^{
  NSLog(@"mission--3");
});
    
dispatch_barrier_async(queue, ^{
  for (int i = 0; i < 10000; i++) {
    if (700 == i) {
      NSLog(@"point1");
    } else if (800 == i) {
      NSLog(@"point2");
    } else if (900 == i) {
      NSLog(@"point3");
    }
  }
NSLog(@"barrier");
});
    
NSLog(@"===aaa===");
    
dispatch_async(queue, ^{
  NSLog(@"mission--4");
});
    
NSLog(@"===bbb===");
    
dispatch_async(queue, ^{
  NSLog(@"mission--5");
});
    
dispatch_async(queue, ^{
  NSLog(@"mission--6");
});

运行结果

dispatch_barrier_async_01.png

dispatch_barrier_async_02.png

分析
根据打印结果可以看出
1.dispatch_barrier_sync/dispatch_barrier_async都是等待当前队列的之前插入的任务执行完之后再执行自己的任务,执行完自己的任务之后再执行当前队列的之后插入的任务。
2.aaa、bbb的输出位置完全不同
sync的时候,aaa、bbb的输出位置都是在任务0结束之后;
=async的时候,aaa、bbb的输出位置可以在任务0结束之前或者任务0正在执行中,又或者任务0结束之后。
3.barrier(dispatch_barrier_sync/dispatch_barrier_async)只是阻塞同队列中后面的操作而已,而dispatch_barrier_async不阻塞当前的线程。

结论
共同点
1.等待在它前面插入队列的任务先执行完;
2.等待他们自己的任务执行完再执行后面的任务。

不同点
1.dispatch_barrier_sync将自己的任务插入到队列的时候,需要等待自己的任务结束之后才会继续插入被写在它后面的任务,然后执行它们;
2.dispatch_barrier_async将自己的任务插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务插入到队列,然后等待自己的任务结束后才执行后面任务;
3.dispatch_barrier_async不阻塞当前的线程,而dispatch_barrier_sync/dispatch_barrier_async只是阻塞同队列中后面的任务。

最后

若是本文内容有错误的地方,还请各位路过的大神们指点指点~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容