dispatch_semaphore是GCD采用線程同步的一種方式,與他相關的共有三個參數:
dispatch_semaphore_create
dispatch_semaphore_signal
dispatch_semaphore_wait
- dispatch_semaphore_create 創建信號量
dispatch_semaphore_create(long value); 給信號量初始一個值,當傳遞的值小于0,信號量將初始化失敗返回NULL。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(2);
- dispatch_semaphore_signal 發送信號量
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
這個函數會使傳入的信號量dsema的值加1;
dispatch_semaphore_signal(semaphore);
- dispatch_semaphore_wait 等待信號量
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
這個函數會使傳入的信號量dsema的值減1,如果傳入信號量的值等于0,函數將持續等待不返回。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
這個函數的作用是這樣的,如果dsema信號量的值大于0,該函數所處線程就繼續執行下面的語句,并且將信號量的值減1;
如果desema的值為0,那么這個函數就阻塞當前線程等待timeout(注意timeout的類型為dispatch_time_t,
不能直接傳入整形或float型數),如果等待的期間desema的值被dispatch_semaphore_signal函數加1了,
且該函數(即dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續向下執行并將信號量減1。
如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執行其后語句。
- 信號量的初始值,可以用來控制線程并發訪問的最大數量。
- 信號量的初時值為1,代表同時只允許1條線程訪問資源,保證線程同步。
iOS線程同步方案性能比較
性能從高到低排序
os_unfair_lock // 缺點:iOS10才支持
OSSpinLock // 缺點:可能出現優先級反轉 已經不再安全 蘋果也不推薦使用
dispatch_semaphore // 推薦使用
pthread_mutex // 優點:跨平臺 互斥鎖(普通鎖) 推薦使用
dispatch_queue(DISPATCH_QUEUE_SERIAL) // c
NSLock // oc
NSCondition // oc
pthread_mutex(recursive) // 遞歸鎖
NSRecursiveLock // oc
NSConditionLock // oc
@synchronized // 遞歸鎖 oc
從上可以知道線程同步除了os_unfair_lock 和
OSSpinLock之外,dispatch_semaphore的性能是很好的極力推薦使用。
如以上假如創建了A、B 、C、D四個子線程,假如A線程先執行了35行代碼后,此時信號量的值-1也就是1,并往下繼續執行。隨后B線程也執行了35行代碼后,此時信號量的值-1,也就是0。C線程、D線程都執行35行代碼時,此時信號量的值已經是0了,C線程和D線程就會進入休眠等待中,此時就卡住35行代碼處。假如第11秒時,線程A、B執行完了第39行代碼后,信號量+1,+1此時信號量就是2了,這樣C線程、D線程會被喚醒繼續執行。這樣信號量的初始值,可以用來控制線程并發訪問的最大數量。同理當我們設置信號量的初時值為1時,就可以實現線程同步。
dispatch_semaphore 使用案例
控制線程并發數
//定義一個信號量,初始化為10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
//同時執行100個任務
for (int i = 0; i < 100; i++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//當前信號量-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務%d執行",i+1);
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:kUrlString]];
dispatch_async(dispatch_get_main_queue(), ^{
//TODO:刷新界面
});
//當前信號量+1
dispatch_semaphore_signal(semaphore);
});
}
線程安全
通過信號量可以用來控制線程并發訪問的最大數量,當我們設置信號量的初時值為1時,就可以實現線程同步。
- (void)viewDidLoad {
[super viewDidLoad];
semaphore = dispatch_semaphore_create(1);
self.array = [NSMutableArray array];
for (int i=0; i<100; i++) {
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil];
[thread start];
}
}
- (void)test{
NSLog(@"測試開始");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
sleep(3);
[self.array addObject:@"0"];
dispatch_semaphore_signal(semaphore);
NSLog(@"測試");
}
self.array直接在多個線程上進行做修改操作是不安全的,我可以通過信息量同步加鎖保證其線程安全。
多個網絡請求同步
- 1、首先通過網絡請求一獲取用戶useid,之后用userid為參數發起網絡請求二。
#pragma mark - 網絡請求一
- (void)getuserId:(dispatch_semaphore_t)semaphore{
AFHTTPSessionManager *sessionmanger=[[AFHTTPSessionManager alloc]init];
sessionmanger.responseSerializer=[AFHTTPResponseSerializer serializer];
[sessionmanger POST:@"https://www.baidu.com/" parameters:nil constructingBodyWithBlock:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功1%@", [NSThread currentThread]);
useid=@"1234";
dispatch_semaphore_signal(semaphore);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error);
dispatch_semaphore_signal(semaphore);
}];
}
#pragma mark - 網絡請求二
- (void)requestwithuserid:(NSString *)userid{
NSDictionary *parms=[NSMutableDictionary dictionary];
[parms setValue:userid forKey:@"userid"];
AFHTTPSessionManager *sessionmanger=[[AFHTTPSessionManager alloc]init];
sessionmanger.responseSerializer=[AFHTTPResponseSerializer serializer];
[sessionmanger POST:@"https://www.baidu.com/" parameters:userid constructingBodyWithBlock:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功2");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
#pragma mark - 使用信號量實現
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_semaphore_t semaphore= dispatch_semaphore_create(0); // 創建信號量
[self getuserId:semaphore];//獲取用戶useid
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);//當前信號量為0,一直等待阻塞線程
[self requestwithuserid:useid];
}
command+R運行一下,沒有任何反應。
原因分析:線程卡住了。代碼執行到dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)因為信號量為0,當前線程會被阻塞。而當前線程是主線程,網絡請求一成功后回調到主線程,因為主線程被阻塞 造成信號量無法釋放,一直卡住。
解決方案就是開啟一個異步線程
- (void)viewDidLoad {
[super viewDidLoad];
//創建一個并行隊列
dispatch_queue_t queque = dispatch_queue_create("GoyakodCreated", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queque, ^{
dispatch_semaphore_t semaphore= dispatch_semaphore_create(0); // 創建信號量
[self getuserId:semaphore];
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
[self requestwithuserid:useid];
});
}
- 2、多個網絡請求后刷新UI
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_group_t group = dispatch_group_create();
// 創建信號量
semaphore = dispatch_semaphore_create(0);
// 創建全局并行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
[self request1];
});
dispatch_group_async(group, queue, ^{
[self request2];
});
dispatch_group_notify(group, queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//在這里 進行請求后的方法,回到主線程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
NSLog(@"12344");
}
- (void)request1{
AFHTTPSessionManager *manger=[[AFHTTPSessionManager alloc]init];
manger.responseSerializer=[AFHTTPResponseSerializer serializer];
[manger POST:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功1");
dispatch_semaphore_signal(semaphore);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(semaphore);
}];
}
- (void)request2{
AFHTTPSessionManager *manger=[[AFHTTPSessionManager alloc]init];
manger.responseSerializer=[AFHTTPResponseSerializer serializer];
[manger POST:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功2");
dispatch_semaphore_signal(semaphore);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(semaphore);
}];
}
或者這樣
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_group_t group = dispatch_group_create();
// 創建全局并行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
[self request1];
});
dispatch_group_async(group, queue, ^{
[self request2];
});
dispatch_group_notify(group, queue, ^{
//在這里 進行請求后的方法,回到主線程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
NSLog(@"12344");
}
- (void)request1{
// 創建信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
AFHTTPSessionManager *manger=[[AFHTTPSessionManager alloc]init];
manger.responseSerializer=[AFHTTPResponseSerializer serializer];
[manger POST:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功1");
NSLog(@"%ld", dispatch_semaphore_signal(semaphore));
;
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(semaphore);
NSLog(@"%@",semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
- (void)request2{
// 創建信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
AFHTTPSessionManager *manger=[[AFHTTPSessionManager alloc]init];
manger.responseSerializer=[AFHTTPResponseSerializer serializer];
[manger POST:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功2");
dispatch_semaphore_signal(semaphore);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
@end
參考:CodeVicent