dispatch_semaphore是GCD采用線程同步的一種方式,與他相關(guān)的共有三個(gè)參數(shù):
dispatch_semaphore_create
dispatch_semaphore_signal
dispatch_semaphore_wait
- dispatch_semaphore_create 創(chuàng)建信號(hào)量
dispatch_semaphore_create(long value); 給信號(hào)量初始一個(gè)值噩死,當(dāng)傳遞的值小于0焙压,信號(hào)量將初始化失敗返回NULL怕篷。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(2);
- dispatch_semaphore_signal 發(fā)送信號(hào)量
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
這個(gè)函數(shù)會(huì)使傳入的信號(hào)量dsema的值加1;
dispatch_semaphore_signal(semaphore);
- dispatch_semaphore_wait 等待信號(hào)量
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
這個(gè)函數(shù)會(huì)使傳入的信號(hào)量dsema的值減1揍障,如果傳入信號(hào)量的值等于0脉课,函數(shù)將持續(xù)等待不返回救军。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
這個(gè)函數(shù)的作用是這樣的财异,如果dsema信號(hào)量的值大于0,該函數(shù)所處線程就繼續(xù)執(zhí)行下面的語句唱遭,并且將信號(hào)量的值減1戳寸;
如果desema的值為0,那么這個(gè)函數(shù)就阻塞當(dāng)前線程等待timeout(注意timeout的類型為dispatch_time_t拷泽,
不能直接傳入整形或float型數(shù))疫鹊,如果等待的期間desema的值被dispatch_semaphore_signal函數(shù)加1了,
且該函數(shù)(即dispatch_semaphore_wait)所處線程獲得了信號(hào)量司致,那么就繼續(xù)向下執(zhí)行并將信號(hào)量減1拆吆。
如果等待期間沒有獲取到信號(hào)量或者信號(hào)量的值一直為0,那么等到timeout時(shí)脂矫,其所處線程自動(dòng)執(zhí)行其后語句枣耀。
- 信號(hào)量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量庭再。
- 信號(hào)量的初時(shí)值為1捞奕,代表同時(shí)只允許1條線程訪問資源,保證線程同步拄轻。
iOS線程同步方案性能比較
性能從高到低排序
os_unfair_lock // 缺點(diǎn):iOS10才支持
OSSpinLock // 缺點(diǎn):可能出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn) 已經(jīng)不再安全 蘋果也不推薦使用
dispatch_semaphore // 推薦使用
pthread_mutex // 優(yōu)點(diǎn):跨平臺(tái) 互斥鎖(普通鎖) 推薦使用
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的性能是很好的極力推薦使用。
如以上假如創(chuàng)建了A哺眯、B 谷浅、C、D四個(gè)子線程奶卓,假如A線程先執(zhí)行了35行代碼后一疯,此時(shí)信號(hào)量的值-1也就是1,并往下繼續(xù)執(zhí)行夺姑。隨后B線程也執(zhí)行了35行代碼后墩邀,此時(shí)信號(hào)量的值-1,也就是0盏浙。C線程眉睹、D線程都執(zhí)行35行代碼時(shí),此時(shí)信號(hào)量的值已經(jīng)是0了废膘,C線程和D線程就會(huì)進(jìn)入休眠等待中竹海,此時(shí)就卡住35行代碼處。假如第11秒時(shí)丐黄,線程A斋配、B執(zhí)行完了第39行代碼后,信號(hào)量+1,+1此時(shí)信號(hào)量就是2了,這樣C線程艰争、D線程會(huì)被喚醒繼續(xù)執(zhí)行坏瞄。這樣信號(hào)量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量甩卓。同理當(dāng)我們設(shè)置信號(hào)量的初時(shí)值為1時(shí)鸠匀,就可以實(shí)現(xiàn)線程同步。
dispatch_semaphore 使用案例
控制線程并發(fā)數(shù)
//定義一個(gè)信號(hào)量逾柿,初始化為10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
//同時(shí)執(zhí)行100個(gè)任務(wù)
for (int i = 0; i < 100; i++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//當(dāng)前信號(hào)量-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務(wù)%d執(zhí)行",i+1);
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:kUrlString]];
dispatch_async(dispatch_get_main_queue(), ^{
//TODO:刷新界面
});
//當(dāng)前信號(hào)量+1
dispatch_semaphore_signal(semaphore);
});
}
線程安全
通過信號(hào)量可以用來控制線程并發(fā)訪問的最大數(shù)量缀棍,當(dāng)我們設(shè)置信號(hào)量的初時(shí)值為1時(shí),就可以實(shí)現(xiàn)線程同步机错。
- (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直接在多個(gè)線程上進(jìn)行做修改操作是不安全的睦柴,我可以通過信息量同步加鎖保證其線程安全。
多個(gè)網(wǎng)絡(luò)請求同步
- 1毡熏、首先通過網(wǎng)絡(luò)請求一獲取用戶useid,之后用userid為參數(shù)發(fā)起網(wǎng)絡(luò)請求二侣诵。
#pragma mark - 網(wǎng)絡(luò)請求一
- (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 - 網(wǎng)絡(luò)請求二
- (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 - 使用信號(hào)量實(shí)現(xiàn)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_semaphore_t semaphore= dispatch_semaphore_create(0); // 創(chuàng)建信號(hào)量
[self getuserId:semaphore];//獲取用戶useid
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);//當(dāng)前信號(hào)量為0痢法,一直等待阻塞線程
[self requestwithuserid:useid];
}
command+R運(yùn)行一下,沒有任何反應(yīng)杜顺。
原因分析:線程卡住了财搁。代碼執(zhí)行到dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)因?yàn)樾盘?hào)量為0,當(dāng)前線程會(huì)被阻塞躬络。而當(dāng)前線程是主線程尖奔,網(wǎng)絡(luò)請求一成功后回調(diào)到主線程,因?yàn)橹骶€程被阻塞 造成信號(hào)量無法釋放穷当,一直卡住提茁。
解決方案就是開啟一個(gè)異步線程
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建一個(gè)并行隊(duì)列
dispatch_queue_t queque = dispatch_queue_create("GoyakodCreated", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queque, ^{
dispatch_semaphore_t semaphore= dispatch_semaphore_create(0); // 創(chuàng)建信號(hào)量
[self getuserId:semaphore];
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
[self requestwithuserid:useid];
});
}
- 2、多個(gè)網(wǎng)絡(luò)請求后刷新UI
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_group_t group = dispatch_group_create();
// 創(chuàng)建信號(hào)量
semaphore = dispatch_semaphore_create(0);
// 創(chuàng)建全局并行
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);
//在這里 進(jìn)行請求后的方法馁菜,回到主線程
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();
// 創(chuàng)建全局并行
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, ^{
//在這里 進(jìn)行請求后的方法茴扁,回到主線程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
NSLog(@"12344");
}
- (void)request1{
// 創(chuàng)建信號(hào)量
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{
// 創(chuàng)建信號(hào)量
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