iOS 開發之 GCD 不同場景使用
本文在iOS 開發值 GCD 基礎 的基礎上,繼續總結了 GCD 的一些API 和在不同場景下的使用。
GCD 柵欄方法:dispatch_barrier_async
我們有時需要異步執行兩組操作,而且第一組操作執行完之后,才能開始執行第二組操作。這樣我們就需要一個相當於 柵欄 一樣的一個方法將兩組異步執行的操作組給分割起來,當然這里的操作組里可以包含一個或多個任務。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄。
dispatch_barrier_async函數會等待前邊追加到並發隊列中的任務全部執行完畢之后,再將指定的任務追加到該異步隊列中。然后在dispatch_barrier_async函數追加的任務執行完畢之后,異步隊列才恢復為一般動作,接着追加任務到該異步隊列並開始執行.
示意如圖:
/**
* 柵欄方法 dispatch_barrier_async
*/
- (void)barrier {
// 1. 創建並發隊列隊列
dispatch_queue_t queue = dispatch_queue_create("com.xiaoyouPrince", DISPATCH_QUEUE_CONCURRENT);
// 2. 添加異步任務
dispatch_async(queue, ^{
// 追加任務1
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
// 柵欄任務
dispatch_barrier_async(queue, ^{
// 追加任務 barrier
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務4
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印當前線程
}
});
}
------------------打印結果-----------------
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
barrier---{number = 4, name = (null)}
barrier---{number = 4, name = (null)}
4---{number = 3, name = (null)}
3---{number = 4, name = (null)}
4---{number = 3, name = (null)}
3---{number = 4, name = (null)}
------------------打印結果-----------------
在dispatch_barrier_async相關代碼執行結果中可以看出:
在執行完柵欄前面的操作之后,才執行柵欄操作,最后再執行柵欄后邊的操作
GCD 延時執行方法:dispatch_after
我們經常會遇到這樣的需求:在指定時間(例如3秒)之后執行某個任務。可以用 GCD 的dispatch_after函數來實現。
需要注意的是:dispatch_after函數並不是在指定時間之后才開始執行處理,而是在指定時間之后將任務追加到主隊列中。嚴格來說,這個時間並不是絕對准確的,但想要大致延遲執行任務,dispatch_after函數是很有效的。
/**
* 延時執行方法 dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"after---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]); // 打印當前線程
});
}
輸出結果:
2018-08-29 17:53:08.713784+0800 GCD-demo[20282:5080295] currentThread---{number = 1, name = main}
2018-08-29 17:53:08.713962+0800 GCD-demo[20282:5080295] after---begin
2018-08-29 17:53:10.714283+0800 GCD-demo[20282:5080295] after---{number = 1, name = main}
在dispatch_after相關代碼執行結果中可以看出:在打印 after---begin 之后大約 2.0 秒的時間,打印了 after---{number = 1, name = main}
GCD 一次性代碼(只執行一次):dispatch_once
我們在創建單例、或者有整個程序運行過程中只執行一次的代碼時,我們就用到了 GCD 的 dispatch_once 函數。使用dispatch_once 函數能保證某段代碼在程序運行過程中只被執行1次,並且即使在多線程的環境下,dispatch_once也可以保證線程安全。
/**
* 一次性代碼(只執行一次)dispatch_once
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執行1次的代碼(這里面默認是線程安全的)
});
}
GCD 快速迭代方法:dispatch_apply
通常我們會用 for 循環遍歷,但是 GCD 給我們提供了快速迭代的函數dispatch_apply。dispatch_apply按照指定的次數將指定的任務追加到指定的隊列中,並等待全部隊列執行結束。
我們可以利用異步隊列同時遍歷。比如說遍歷 0~5 這6個數字,for 循環的做法是每次取出一個元素,逐個遍歷。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");
}
--------------------輸出結果:-------------------
apply---begin
1---{number = 3, name = (null)}
0---{number = 1, name = main}
2---{number = 4, name = (null)}
3---{number = 5, name = (null)}
4---{number = 3, name = (null)}
5---{number = 1, name = main}
apply---end
--------------------輸出結果:-------------------
從 dispatch_apply 相關代碼執行結果中可以看出:
0~5 打印順序不定,最后打印了 apply---end。
因為是在並發隊列中異步隊執行任務,所以各個任務的執行時間長短不定,最后結束順序也不定。但是apply---end一定在最后執行。這是因為 dispatch_apply 函數會等待全部任務執行完畢。
GCD 的隊列組:dispatch_group
有時候我們會有這樣的需求:分別異步執行2個耗時任務,然后當2個耗時任務都執行完畢后再回到主線程執行任務。這時候我們可以用到 GCD 的隊列組。
調用隊列組的 dispatch_group_async 先把任務放到隊列中,然后將隊列放入隊列組中。或者使用隊列組的 dispatch_group_enter、dispatch_group_leave 組合 來實現dispatch_group_async。
調用隊列組的 dispatch_group_notify 回到指定線程執行任務。或者使用 dispatch_group_wait回到當前線程繼續向下執行(會阻塞當前線程)。
dispatch_group_notify
監聽 group 中任務的完成狀態,當所有的任務都執行完成后,追加任務到 group 中,並執行任務。
/**
* 隊列組 dispatch_group_notify
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
// 1. 創建 group
dispatch_group_t group = dispatch_group_create();
// 2. 異步執行組內的全局隊列任務
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務1
for(inti = 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(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步任務1、任務2都執行完畢后,回到主線程執行下邊任務
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
NSLog(@"group---end");
});
}
-------------------輸出結果:-----------------
currentThread---{number = 1, name = main}
group---begin
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
2---{number = 3, name = (null)}
1---{number = 4, name = (null)}
3---{number = 1, name = main}
3---{number = 1, name = main}
group---end
從dispatch_group_notify相關代碼運行輸出結果可以看出:
當所有任務都執行完成之后,才執行 dispatch_group_notify block 中的任務。
dispatch_group_wait
暫停當前線程(阻塞當前線程),等待指定的 group 中的任務執行完成后,才會往下繼續執行。
/**
* 隊列組 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
// 1. 創建group
dispatch_group_t group = dispatch_group_create();
// 2. 異步執行組中全局隊列任務
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務1
for(inti = 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(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
// 等待上面的任務全部完成后,會往下繼續執行(會阻塞當前線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
------------------輸出結果:------------------
currentThread---{number = 1, name = main}
group---begin
2---{number = 4, name = (null)}
1---{number = 3, name = (null)}
2---{number = 4, name = (null)}
1---{number = 3, name = (null)}
group---end
------------------輸出結果:------------------
從dispatch_group_wait相關代碼運行輸出結果可以看出:
當所有任務執行完成之后,才執行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 會阻塞當前線程。
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");
// 1.創建 group
dispatch_group_t group = dispatch_group_create();
// 2. 獲取全局並發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 3. 進入 group 中執行
dispatch_group_enter(group);
// 3.1. 向隊列中異步執行任務
dispatch_async(queue, ^{
// 追加任務1
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
// 3.2. 任務執行之后,離開組【leave 和 enter 必須成對使用】
dispatch_group_leave(group);
});
// 4. 再次進入組執行新的任務
dispatch_group_enter(group);
// 4.1. 向隊列中異步執行任務
dispatch_async(queue, ^{
// 追加任務2
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
// 4.2. 任務執行之后,離開組【leave 和 enter 必須成對使用】
dispatch_group_leave(group);
});
// group 內部的任務都執行完之后通知執行下面代碼
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執行完畢后,回到主線程.
for(inti = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
NSLog(@"group---end");
});
}
-------------------輸出結果------------------
currentThread---{number = 1, name = main}
group---begin
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
1---{number = 4, name = (null)}
2---{number = 3, name = (null)}
3---{number = 1, name = main}
3---{number = 1, name = main}
group---end
-------------------輸出結果------------------
從dispatch_group_enter、dispatch_group_leave相關代碼運行結果中可以看出:當所有任務執行完成之后,才執行 dispatch_group_notify 中的任務。這里的dispatch_group_enter、dispatch_group_leave組合,其實等同於dispatch_group_async。
GCD 信號量:dispatch_semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計數的信號。類似於過高速路收費站的欄桿。可以通過時,打開欄桿,不可以通過時,關閉欄桿。在 Dispatch Semaphore 中,使用計數來完成這個功能,計數為0時等待,不可通過。計數為1或大於1時,計數減1且不等待,可通過。Dispatch Semaphore 提供了三個函數。
dispatch_semaphore_create:創建一個Semaphore並初始化信號的總量
dispatch_semaphore_signal:發送一個信號,讓信號總量加1
dispatch_semaphore_wait:可以使總信號量減1,當信號總量為0時就會一直等待(阻塞所在線程),否則就可以正常執行。
注意:信號量的使用前提是:想清楚你需要處理哪個線程等待(阻塞),又要哪個線程繼續執行,然后使用信號量。
Dispatch Semaphore 在實際開發中主要用於:
保持線程同步,將異步執行任務轉換為同步執行任務
保證線程安全,為線程加鎖
Dispatch Semaphore 線程同步
我們在開發中,會遇到這樣的需求:異步執行耗時任務,並使用異步執行的結果進行一些額外的操作。換句話說,相當於,將將異步執行任務轉換為同步執行任務。比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通過引入信號量的方式,等待異步執行任務結果,獲取到 tasks,然后再返回該 tasks。
- (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 = %zd",number);
}
----------------------輸出結果-----------------
currentThread---{number = 1, name = main}
semaphore---begin
1---{number = 3, name = (null)}
semaphore---end, number = 100
從 Dispatch Semaphore 實現線程同步的代碼可以看到:
semaphore---end 是在執行完number = 100; 之后才打印的。而且輸出結果 number 為 100。這是因為異步執行不會做任何等待,可以繼續執行任務。異步執行將任務1追加到隊列之后,不做等待,接着執行dispatch_semaphore_wait方法。此時 semaphore == 0,當前線程進入等待狀態。然后,異步任務1開始執行。任務1執行到dispatch_semaphore_signal之后,總信號量加1,此時 semaphore == 1,dispatch_semaphore_wait方法檢測到總信號量為1,正在被阻塞的線程(主線程)恢復繼續執行。最后打印semaphore---end,number = 100。這樣就實現了線程同步,將異步執行任務轉換為同步執行任務。
Dispatch Semaphore 線程安全和線程同步(為線程加鎖)
線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。
線程同步:可理解為線程 A 和 線程 B 一塊配合,A 執行到一定程度時要依靠線程 B 的某個結果,於是停下來,示意 B 運行;B 依言執行,再將結果給 A;A 再繼續操作。
舉個簡單例子就是:兩個人在一起聊天。兩個人不能同時說話,避免聽不清(操作沖突)。等一個人說完(一個線程結束操作),另一個再說(另一個線程再開始操作)。
下面,我們模擬火車票售賣的方式,實現 NSThread 線程安全和解決線程同步問題。
場景:總共有50張火車票,有兩個售賣火車票的窗口,一個是北京火車票售賣窗口,另一個是上海火車票售賣窗口。兩個窗口同時售賣火車票,賣完為止。
非線程安全(不使用 semaphore)
先來看看不考慮線程安全的代碼:
/**
* 非線程安全:不使用 semaphore
* 初始化火車票數量、賣票窗口(非線程安全)、並開始賣票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 50; // 初始化車票 50 張
// queue1 代表北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("com.xiaoyouPrince1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火車票售賣窗口
dispatch_queue_t queue2 = dispatch_queue_create("com.xiaoyouPrince2", 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;
}
}
}
----------------- 輸出結果(部分打印) -------------------
currentThread <NSThread: 0x608000079280>{number = 1, name = main}
non_semaphore_begin
剩余票數 48,窗口:{number = 4, name = (null)}
剩余票數 49,窗口:{number = 3, name = (null)}
剩余票數 47,窗口:{number = 4, name = (null)}
剩余票數 46,窗口:{number = 3, name = (null)}
剩余票數 45,窗口:{number = 4, name = (null)}
剩余票數 44,窗口:{number = 3, name = (null)}
剩余票數 43,窗口:{number = 4, name = (null)}
剩余票數 42,窗口:{number = 3, name = (null)}
剩余票數 40,窗口:{number = 4, name = (null)}
剩余票數 41,窗口:{number = 3, name = (null)}
剩余票數 39,窗口:{number = 4, name = (null)}
剩余票數 0,窗口:{number = 3, name = (null)}
...\
票已經售完
票已經售完
----------------- 輸出結果(部分打印) -------------------
可以看到在不考慮線程安全,不使用 semaphore 的情況下,得到票數是錯亂的,這樣顯然不符合我們的需求,所以我們需要考慮線程安全問題。
線程安全(使用 semaphore 加鎖)
考慮線程安全的代碼:
/**
* 線程安全:使用 semaphore 加鎖
* 初始化火車票數量、賣票窗口(線程安全)、並開始賣票
*/
- (void)initTicketStatusSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"semaphore---begin");
// 有成員變量 dispatch_semaphore_t semaphreLock
semaphreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
// queue1 代表北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("com.xiaoyouPrince1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火車票售賣窗口
dispatch_queue_t queue2 = dispatch_queue_create("com.xiaoyouPrince2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
/**
* 售賣火車票(線程安全)
*/
- (void)saleTicketSafe {
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);
}
}
----------------- 輸出結果(部分打印) -------------------
currentThread <NSThread: 0x608000079280>{number = 1, name = main}
non_semaphore_begin
剩余票數 49,窗口:{number = 4, name = (null)}
剩余票數 48,窗口:{number = 3, name = (null)}
剩余票數 47,窗口:{number = 4, name = (null)}
剩余票數 46,窗口:{number = 3, name = (null)}
剩余票數 45,窗口:{number = 4, name = (null)}
....\
剩余票數 4,窗口:{number = 3, name = (null)}
剩余票數 3,窗口:{number = 4, name = (null)}
剩余票數 2,窗口:{number = 3, name = (null)}
剩余票數 1,窗口:{number = 4, name = (null)}
剩余票數 0,窗口:{number = 3, name = (null)}
票已經售完
----------------- 輸出結果(部分打印) -------------------
可以看出,在考慮了線程安全的情況下,使用dispatch_semaphore 機制之后,得到的票數是正確的,沒有出現混亂的情況。我們也就解決了多個線程同步的問題。
創建一個 semaphoreLock 對象。后續可能有多個線程同時訪問被車票余量,當有線程訪問到票余量時進行 dispatch_semaphore_wait 操作。使得 semaphoreLock 總信號量 減 1 (等於0 ,相當於加鎖) ,並繼續操作票余量,進行一次 self.ticketSurplusCount-- 操作。當票數量減少之后執行 dispatch_semaphore_signal 使得 semaphoreLock 總信號量 + 1 (等於1 ,相當於解鎖)
此過程中如果有其他並發線程要訪問 票余量。同樣會先來到 dispatch_semaphore_wait 操作。此時 semaphoreLock 的總信號量為 0 ,直接阻塞線程。當上一個訪問余量的線程操作完成之后執行 dispatch_semaphore_signal 操作解鎖之后,就會繼續本線程的訪問。從而確保了票余量的線程安全。
資料:
最新官方文檔