一.GCD的簡介
Grand Central Dispatch(GCD)是 Apple 開發的一個多核編程的較新的解決方法。它主要用于優化應用程序以支持多核處理器以及其他對稱多處理系統。它是一個在線程池模式的基礎上執行的并發任務。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。
在GCD中有兩種隊列:串行隊列和并發隊列。兩者都符合FIFO(先進先出)的原則,兩者的主要區別是:執行順序不同,以及開啟線程數不同。
串行隊列(Serial Dispaych Queue):
每次只有一個任務被執行。讓任務一個接著一個地執行。(只開啟一個線程,一個任務執行完畢后,在執行下一個任務)
并發隊列(ConCurrent Dispatch Queue)
可以讓多個任務并發(同時)執行。(可以開啟多個線程,并且同時執行任務)
注意:并發隊列的并發功能只有在異步(dispatch_async)函數下才有效


三、GCD 的使用步驟
1.創建一個隊列(串行隊列或并發隊列)
2.將任務追加到任務的等待隊列中,然后系統就會根據任務類型執行任務(同步執行或異步執行)
隊列的創建方法/獲取方法
可以使用dispatch_queue_create來創建隊列,需要傳入兩個參數,第一個參數表示隊列的唯一標識符,用于debug,可為空,Dispatch Queue的名稱推薦使用應用程序ID這種逆序全程域名;第二個參數用來識別是串行隊列還是并發隊列。DISPATCH_QUEUE_SERIAL 表示串行隊列,DISPATCH_QUEUE_CONCURRENT表示并發隊列。
// 串行隊列的創建方法
dispatch_queue_t queue = dispatch_queue_create("net.nange.test", DISPATCH_QUEUE_SERIAL);
// 并發隊列的創建方法
dispatch_queue_t queue1 = dispatch_queue_create("net.nange.test", DISPATCH_QUEUE_CONCURRENT)
對于串行隊列,GCD提供了一種特殊的串行隊列:主隊列(Main Dispatch QUeue)
所有放在主隊列中的任務,都會放到主線程中執行
可使用dispatch_get_main_queue()獲得主隊列
// 主隊列的獲取方法
dispatch_queue_t queue = dispatch_get_main_queue();
對于并發隊列,GCD默認提供了全局并發隊列(Global Dispatch Queue)
可以使用dispatch_get_global_queue來獲取。需要傳入兩個參數。第一個參數表示隊列的優先級,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個參數暫時沒用,用0即可。
//全局并發隊列的獲取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
任務的創建方法
GCD提供了同步執行任務的創建方法dispatch_sync和異步執行任務創建方法dispatch_async.
// 同步執行任務創建方法
dispatch_sync(queue, ^{
// 這里放同步執行任務代碼
});
// 異步執行任務創建方法
dispatch_async(queue, ^{
// 這里放異步執行任務代碼
});
雖然使用GCD只需兩步,但是既然我們有兩種隊列(串行隊列/并發隊列),兩種任務執行方式(同步執行/異步執行),那么我們就有了四種不同的組合方式。這四種不同的組合方式是:
1.同步執行 + 并發隊列
2.異步執行 + 并發隊列
3.同步執行 + 串行隊列
4.異步執行 + 串行隊列
實際上,剛才還說了兩種特殊的隊列:全局并發隊列、主隊列。全局并發隊列可以作為普通并發隊列來使用。但是主隊列因為有點特殊,所以我們就又多了兩種組合方式。這樣就有六種不同的組合方式了。
5.同步執行 + 主隊列
6.異步執行 + 主隊列
那么這幾種不同的組合方式各有什么區別呢,直接看結果 再做講解

四.GCD的基本使用
先來講講并發隊列的兩種執行方式
4.1 同步執行 + 并發隊列
在當前線程中執行任務,不會開啟新線程,執行完一個任務,在執行下一個任務
/*
同步執行 + 并發隊列
特點:在當前線程中執行任務,不會開啟新線程,執行完一個任務,在執行下一個任務
*/
-(void)syncConcurrent{
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"syncConcurrent--begin");
dispatch_queue_t queue = dispatch_queue_create("net.nange.QQ", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 追加任務1
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"1----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]);//打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i=0 ; i<2; i++) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]);//打印當前線程
}
});
NSLog(@"syncCurrent-----end");
}
4.2 異步執行 + 并發隊列
可以開啟多個線程,任務交替(同時執行)
/*
異步執行 + 并發隊列
特點:可以開啟多個線程,任務交替(同時執行)
*/
-(void)asyncConcurrent{
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"asyncConcurrent--begin");
dispatch_queue_t queue = dispatch_queue_create("net.nange.QQ", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任務1
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"1----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]);//打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i=0 ; i<2; i++) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]);//打印當前線程
}
});
NSLog(@"syncCurrent-----end");
}
4.3 同步執行 + 串行隊列
不會開啟新線程,在當前線程執行任務。任務是串行的,執行完一個任務,在執行下一個任務
/*
同步執行 + 串行隊列
特點:不回開啟新線程,在當前線程執行任務中。任務是串行的,在執行完一個任務,在執行下一個任務
*/
-(void)syncSerial{
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"syncSerial--begin");
dispatch_queue_t queue = dispatch_queue_create("net.nange.QQ", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任務1
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"1----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]);//打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i=0 ; i<2; i++) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]);//打印當前線程
}
});
NSLog(@"syncCurrent-----end");
}
4.4 異步執行 + 串行隊列
會開啟新線程,但是因為任務是串行的,執行完一個任務,在執行下一個任務
/*
異步執行 + 串行隊列
特點:會開啟新線程,但是因為任務是串行的,執行完一個任務,在執行下一個任務
*/
-(void)asyncSerial{
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"asyncSerial--begin");
dispatch_queue_t queue = dispatch_queue_create("net.nange.QQ", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任務1
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"1----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]);//打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i=0 ; i<2; i++) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]);//打印當前線程
}
});
NSLog(@"syncCurrent-----end");
}
主隊列:GCD自帶的一種特殊的串行隊列
所有放在主隊列中的任務,都會放到主線程中執行
可使用disPatch_get_main_queue()獲得主隊列
4.5 同步執行 + 主隊列
同步執行 + 主隊列 在不用線程中調用結果也是不一樣,在主線程中調用會出現死鎖,而在其他線程中 則不會。
4.5.1 在主線程中調用 (同步執行 + 主隊列)
互相等待卡住不可行
/*
同步執行 + 主隊列
特點(主線程調用):互等卡住不執行
特點(其他線程調用):不會開啟新線程,執行完一個任務,再執行下一個任務。
*/
-(void)syncMain{
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"asyncSerial--begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任務1
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"1----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]);//打印當前線程
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i=0 ; i<2; i++) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]);//打印當前線程
}
});
NSLog(@"syncCurrent-----end");
}
4.5.2 在其他線程中調用
不會開啟新線程,執行完一個任務,在執行下一個任務
使用NSThread 的detachNewthreadSelect 方法會創建線程,并自動啟動線程執行
selector 任務
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
4.6 異步執行 + 主隊列
只有在主線程中執行任務,執行完一個任務,在執行下一個任務
/*
異步執行 + 主隊列
特點:只在主線程中執行任務,執行完一個任務,在執行下一個任務
*/
-(void)asyncMain{
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"asyncSerial--begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 追加任務1
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"1----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]);//打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i=0 ; i<2; i++) {
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]);//打印當前線程
}
});
NSLog(@"syncCurrent-----end");
}
5.GCD線程間的通信
iOS中 我們一般在主線程里邊進行UI刷新,例如:點擊、滾動、拖拽等事件。我們通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而我們有時候在其他線程完成了耗時操作,需要回到主線程,那么就用到了線程之間的通訊。
-(void)asyncMain{
// 獲取全局并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 異步 追加任務
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"1----%@",[NSThread currentThread]); //打印當前線程
}
// 回到主線程
dispatch_async(mainQueue, ^{
// 追加在主線程中執行的任務
[NSThread sleepForTimeInterval:2];//模擬耗時操作
NSLog(@"2-----%@",[NSThread currentThread]);//打印當前線程
});
});
}
6.GCD的其他方法
6.1 GCD柵欄方法:dispatch_barrier_async
/*
柵欄方法
*/
-(void)barrier{
dispatch_queue_t queue = dispatch_queue_create("net.nange.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 異步 追加任務1
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"1----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_async(queue, ^{
// 異步 追加任務2
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"2----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_barrier_async(queue, ^{
// 追加任務barrier
for(int i=0;i<2;i++){
[NSThread sleepForTimeInterval:2];
NSLog(@"barrier-----%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
// 異步 追加任務3
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"3----%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_async(queue, ^{
// 異步 追加任務4
for (int i=0; i<2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操作
NSLog(@"4----%@",[NSThread currentThread]); //打印當前線程
}
});
}
6.2 GCD延時執行方法:dispatch_after
指定時間(5秒)之后執行某個任務??梢杂肎CD的dispatch_after函數實現。
需要注意的是:dispatch_after函數并不是在指定時間之后才開始處理,而是在指定時間之后將任務追加到隊列中。嚴格來說,這個時間并不是絕對準確的,但想要大致延遲執行任務,dispatch_after函數是很有效的。
/*
延時執行方法 diapatch_after
*/
-(void)after{
NSLog(@"currerntThread---%@",[NSThread currentThread]);//打印當前線程
NSLog(@"asyncMain--begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
// 2.0秒后異步追加任務代碼到主隊列,并開始執行
NSLog(@"after----%@",[NSThread currentThread]);//打印當前線程
});
}
6.3 GCD 一次性代碼(只執行一次):dispatch_once
我們在創建單例、或者有整個程序運行過程中只執行一次的代碼時,我們就用到了GCD的dispatch_once函數。使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行一次,并且即使在多線程的環境下,dispatch_once也可以保證線程的安全。
/*
一次性代碼(只執行一次)dispatch_once
*/
-(void)once{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//只執行一次的代碼(這里面默認是線程安全的)
});
}
6.4 GCD快速迭代方法:dispatch_apply
通常我們會用for循環,但是GCD給我們提供了快速迭代的函數dispatch_apply.dispatch_apply按照指定的次數將指定的任務追加到指定的隊列中,并等待全部隊列執行結束。
/*
快速迭代方法 dispatch_apply
*/
-(void)apply{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply----begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd-----%@",index,[NSThread currentThread]);
});
NSLog(@"apply----end");
}
6.5 GCD隊列組:dispatch_group
需求:分別異步執行2個耗時任務,然后當2個耗時任務都執行完畢后再回到主線程執行任務。這時候我們可以使用到GCD的隊列組
6.5.1 dispatch_group_notify
監聽group中任務的完成狀態,當所有的任務都快執行完成后,追加任務到group中,并執行任務
/*
隊列組 dispatch_group_notify
*/
-(void)groupNotify{
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步任務1、任務2都執行完畢后,回到主線程執行下邊任務
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
NSLog(@"group---end");
});
}
6.5.2 dispat_group_wait
暫停當前線程(阻塞當前線程),等待指定的group中的任務執行完成后,才會往下繼續執行
/**
* 隊列組 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
// 等待上面的任務全部完成后,會往下繼續執行(會阻塞當前線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
6.5.3 dispatch_group_enter、dispatch_group_leave
dispatch_group_enter標志著一個任務追加到 group,執行一次,相當于 group 中未執行完畢任務數+1
dispatch_group_leave標志著一個任務離開了 group,執行一次,相當于 group 中未執行完畢任務數-1。
當 group 中未執行完畢任務數為0的時候,才會使dispatch_group_wait解除阻塞,以及執行追加到dispatch_group_notify中的任務。
/**
* 隊列組 dispatch_group_enter、dispatch_group_leave
*/
- (void)groupEnterAndLeave
{
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執行完畢后,回到主線程.
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
NSLog(@"group---end");
});
// // 等待上面的任務全部完成后,會往下繼續執行(會阻塞當前線程)
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//
// NSLog(@"group---end");
}
6.6 GCD信號量 :dispatch_semaphoreDispatch Semaphore提供了三個函數。
dispatch_semaphore_create:創建一個Semaphore并初始化信號的總量
dispatch_semaphore_signal:發送一個信號,讓信號總量加1
dispatch_semaphore_wait:可以使總信號量減1,當信號總量為0時就會一直等待(阻塞所在線程),否則就可以正常執行。
注意:信號量的使用前提是:想清楚你需要處理哪個線程等待(阻塞),又要哪個線程繼續執行,然后使用信號量。
Dispatch Semaphore 在實際開發中主要用于:
- 保持線程同步,將異步執行任務轉換為同步執行任務
- 保證線程安全,為線程加鎖
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
接下來,我們利用Dispatch Semaphore實現線程同步,將異步執行任務轉換為同步執行任務。
/**
* semaphore 線程同步
*/
- (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任務1
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %d",number);
}
6.6.2 Dispatch Semaphore線程安全和線程同步(為線程加鎖)
線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行結果一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的
若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。
線程同步:可理解為線程 A 和 線程 B 一塊配合,A 執行到一定程度時要依靠線程 B 的某個結果,于是停下來,示意 B 運行;B 依言執行,再將結果給 A;A 再繼續操作。
舉個簡單例子就是:兩個人在一起聊天。兩個人不能同時說話,避免聽不清(操作沖突)。等一個人說完(一個線程結束操作),另一個再說(另一個線程再開始操作)。
下面,我們模擬火車票售賣的方式,實現 NSThread 線程安全和解決線程同步問題。
場景:總共有50張火車票,有兩個售賣火車票的窗口,一個是北京火車票售賣窗口,另一個是上?;疖嚻笔圪u窗口。兩個窗口同時售賣火車票,賣完為止。
/**
* 非線程安全:不使用 semaphore
* 初始化火車票數量、賣票窗口(非線程安全)、并開始賣票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 50;
// queue1 代表北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上?;疖嚻笔圪u窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
* 售賣火車票(非線程安全)
*/
- (void)saleTicketNotSafe {
while (1) {
if (self.ticketSurplusCount > 0) { //如果還有票,繼續售賣
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { //如果已賣完,關閉售票窗口
NSLog(@"所有火車票均已售完");
break;
}
}
}
可以看到在不考慮線程安全,不使用 semaphore 的情況下,得到票數是錯亂的,這樣顯然不符合我們的需求,所以我們需要考慮線程安全問題。
6.6.2.2 線程安全(使用semaphore加鎖)
考慮線程安全的代碼
/**
* 線程安全:使用 semaphore加鎖
* 初始化火車票數量、賣票窗口(線程安全)、并開始賣票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
// queue1 代表北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上?;疖嚻笔圪u窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
* 售賣火車票(非線程安全)
*/
- (void)saleTicketNotSafe {
while (1) {
// 相當于加鎖
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { //如果還有票,繼續售賣
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { //如果已賣完,關閉售票窗口
NSLog(@"所有火車票均已售完");
// 相當于解鎖
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相當于解鎖
dispatch_semaphore_signal(semaphoreLock);
}
}
浙公網安備 33010602011771號