同步與阻塞的區別與聯絡
何謂同步
在大多數情況下,同步
是相對於非同步
而言的。在iOS開發中,同步主要涉及兩個語義:同步呼叫
與執行緒同步
。這裡我們主要討論的是執行緒同步。
同步呼叫
同步呼叫
指的是我們在呼叫一個函式時,立刻可以得到執行結果,並進行後續的操作。
NSString *message = [self getMessage];
// do something
複製程式碼
與之相對的,非同步呼叫
指的是在我們呼叫一個函式的時候,並不能立刻得到執行結果。而是在執行完成時,通過回撥函式、代理事件、通知等方式告知我們,並進行後續操作。
[self fetchMesssageWithCompletion:^(NSString *message) {
// do something
}];
[self getMessage];
- (void)didReceiveMessage:(NSString *)message
{
// do something
}
複製程式碼
執行緒同步
執行緒同步
,一種執行緒間的直接制約關係
,是指多個執行緒(或程式)為了合作完成任務,步調一致的執行。
- 同一時刻,只能有一個執行緒執行。有一個執行緒在執行,則其他執行緒等待。
- 執行緒之間可以指定特定的順序依次執行,也可以在當前執行執行緒完成之後,進行競爭
值得注意的是只有一個執行緒,或者相同的執行緒之間是不能稱之為同步的。
何時需要執行緒同步
執行緒間的通訊與資源共享
多個執行緒之間要想步調一致的執行,必須保持彼此之間的有效溝通。尤其是線上程之間存在著一個或多個共享資源時。
- 保持各個執行緒之間共享資源的資料一致性
- 其他執行緒對於共享資源的操作,必須及時告之其他執行緒。
執行緒間存在某種關聯
如果兩個執行緒之間沒有任何關聯,那麼他們之間的同步是毫無意義的。如果兩個或多個執行緒是同步的那麼他們之間必然存在著某種關聯。
- 一個執行緒的執行,會對其他執行緒的執行結果產生影響。
- 一個執行緒的執行依賴另一個執行緒的執行結果。
值得注意的是,現實開發中,這種關聯存在一執行緒當中某些關鍵任務與另一個執行緒的某些任務之間,並不存在與整個執行緒生命週期。通常來說是,執行緒間某些任務之間的相互關聯,相互影響。
同步往往伴隨著互斥
如果多個執行緒之間是同步的,那麼他們之間的執行往往是互斥的。也就是說,在任意時刻,他們都不可能同時執行。當一個執行緒正在執行時,其他執行緒必須進入等待狀態,等該執行緒執行結束之後,才以恰當的方式(事件佇列、執行緒優先順序、競態條件)爭奪執行的機會。
值得注意的是,這種互斥往往只在執行某些特定任務時(通常是涉及共享資料的操作)發生。一個執行緒整個生命週期都與其他執行緒互斥,這是很少見的。除非該執行緒只用於處理特定的任務事件。
同步是更為複雜的互斥,同步與互斥的區別參見這篇文章
阻塞
廣義上的阻塞,指的是某一段程式碼、某個任務事件的執行耗費了了大量的時間,或者耗時不確定,阻礙了程式繼續往下執行。
而狹義上的阻塞,等同於執行緒的掛起,指的就是在下述狀態模中進入了阻塞狀態,執行緒掛起,等待某個事件發生以將其喚醒。
![程式/執行緒的狀態模型](
- 執行緒建立成功之後進入就緒狀態,等待作業系統排程
- 系統排程獲得時間片之後,進入執行狀態
- 時間片消耗光之後,進入就緒狀態,繼續等待系統排程
- 當某些事件發生時,譬如在爭奪某個系統資源時,需要等待其他任務完成,或者自己主動掛起。會進入阻塞狀態、或者稱之為掛起狀態。
- 當所要等待的事件發生,或被主動喚醒時,進入就緒狀態,等待作業系統排程
- 當執行緒所有操作結束時,執行緒終止並銷燬。
某些情況下同步意味著阻塞
在通常情況下,同步往往伴隨著執行緒阻塞。如果多個執行緒之間是同步的,當一個執行緒執行時,其他執行緒必須等待。而等待有好幾種方式,通常是執行緒掛起,也就是阻塞。
GCD在同步派發時,當前執行緒會發生阻塞,等待目標執行緒的目標任務執行完畢才喚醒。除了自旋鎖之外,所有用以實現互斥效用的鎖,在實現同步操作時,都會阻塞。
dispatch_queue_t queueA = dispatch_queue_create("A", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queueA, ^{
NSString *message = [self getMessage];
NSLog(@"%@", message);
});
複製程式碼
同步非阻塞情形
在進行執行緒同步時,如果處於等待狀態的執行緒沒有發生阻塞,或則說沒有掛起,繼續暫用著CPU的時間片,即同步非阻塞情形。這種情形一般是異部呼叫,同步輪詢。輪詢包括空轉輪詢、定時循序
- 執行緒在等待時不掛起,而是空轉。用自旋鎖實現同步是很好的例子。也可以自己啟迴圈,譬如下述程式碼示例:
dispatch_queue_t q1 = dispatch_queue_create("1", DISPATCH_QUEUE_SERIAL);
__block int result = 0;
dispatch_async(q1, ^{
sleep(2);
result = 56;
});
while (!result) {
// do something
}
NSLog(@"result = %d",result);
// do something
複製程式碼
- 執行緒不掛起,而是定時監測共享資源的狀態。
dispatch_queue_t q1 = dispatch_queue_create("1", DISPATCH_QUEUE_SERIAL);
__block int result = 0;
dispatch_async(q1, ^{
sleep(2);
result = 56;
});
NSTimer *timer = [NSTimer timerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {
if (result) {
// do something
NSLog(@"result = %d",result);
[timer invalidate];
}
}];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
複製程式碼
阻塞非同步情形
單純將執行緒掛起,而不進行任何執行緒之間的同步操作,即是此種情形。