多线程同步

在进行项目开发时经常会遇到这样一个场景:当多个请求完成时,最后执行某个业务逻辑。一看上去这就是一个多线程控制同步的问题,常见的解决方案有三种。

  • 每个请求定义一个变量,然后在每个请求执行完后的block里执行最后执行业务逻辑的方法,方法里必然有着这几个变量的真判断。
  • 使用队列NSOperationQueue控制多线程同步。
  • 使用信号量dispatch_semaphore_t控制多线程同步。
  • 使用进程同步dispatch_group_t控制多线程同步。

比较及实现方式

  • 第一种看上去很粗浅,就是每次请求就去判断一次各个变量,都通过了表示请求完成了,然后执行最后的业务逻辑。当然这种最好写,但是会加大内存开销和业务复杂度,一方面是有几个请求就得定义几个变量,程序运行时需要进行内存分配,另外还要维护每个变量的真假业务逻辑,如果存在刷新之类的操作更是如此,更有甚者,如果是几十上百个请求,难道要定义几十上百个变量,所以对于简单的多线程请求,稍微多一些或者复杂一些的不建议使用。不过,现在这种方式一般见不到。

  • 第二种队列NSOperationQueue面向对象方式,多用于请求之间存在依赖关系,如果不存在依赖关于请求的完成无法控制,所以不存在依赖则这种方式一般不建议使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSBlockOperation *operation_1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1");
[self loadUserFriendsData];
}];
NSBlockOperation *operation_2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2");
[self loadBalaceData];
}];
NSBlockOperation *operation_3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3");
[self loadUserData];
}];
//设置依赖关系
[operation_2 addDependency:operation_3];
[operation_1 addDependency:operation_2];
//创建队列并添加任务 (这里要设置No)
NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperations:@[operation_3,operation_2,operation_1] waitUntilFinished:NO];
//注意这里一定要是No,不然会阻塞线程
  • 第三种使用信号量dispatch_semaphore_t和使用dispatch_group_t有异曲同工之妙,所以这里直接上代码,怎么使用。
1
2
3
4
5
6
//创建信号
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
//发射信号
dispatch_semaphore_signal(sema);
//等待信号
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(void)request2{
//创建信号量并设置计数默认为0
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSDictionary * para ;//参数 具体部分我就不暴露了
[WJFCollection postWithUrlString:@"url" Parameter:para success:^(id responseObject) {
//只要在这里发送信号
dispatch_semaphore_signal(semaphore);
} failure:^(NSError *error) {
//只要在这里发送信号
dispatch_semaphore_signal(semaphore);
NSLog(@"%@",error);
}];
//若计数为0则一直等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
  • 第四种使用dispatch_group_t,使用方式如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self requestData];
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self requestData];
}) ;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self requestData];
}) ;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//展示页面
[self showUserView];
});
1
2
3
4
5
6
7
8
9
10
- (void)requestData {
dispatch_group_enter(self.group);
WeakSelf
[self.VMModel request:^(id model, NSError * _Nonnull error) {
StrongSelf
if (strongSelf) {
dispatch_group_leave(strongSelf.group);
}
}];
}

dispatch_semaphore和dispatch_group在使用方式上大同小异,但是dispatch_semaphore相对而言更涉及到底层,越底层使用起来越灵活,自定义程度越高,但是使用起来就会越复杂,而dispatch_group是在信号量dispatch_semaphore_t基础上进行的一层封装,所以使用起来方便一些。从使用对象来说,dispatch_group更加针对的是任务、进程而非线程,所以整体性处理起来更快捷。关于两者的层级请看dispatch_group_leave的源码使用。

1
2
3
4
5
6
7
8
9
10
11
12
void dispatch_group_leave(dispatch_group_t dg)
{
dispatch_semaphore_t dsema = (dispatch_semaphore_t)dg;
dispatch_atomic_release_barrier();
long value = dispatch_atomic_inc2o(dsema, dsema_value);//dsema_value原子性加1
if (slowpath(value == LONG_MIN)) {//内存溢出,由于dispatch_group_leave在dispatch_group_enter之前调用
DISPATCH_CLIENT_CRASH("Unbalanced call to dispatch_group_leave()");
}
if (slowpath(value == dsema->dsema_orig)) {//表示所有任务已经完成,唤醒group
(void)_dispatch_group_wake(dsema);
}
}