參考資料:
Semaphore信號量主要用途有兩個:
- 用于多線程對共享資源的訪問控制,保證線程安全,為線程加鎖。
- 保持線程同步,將異步任務(wù)轉(zhuǎn)換為同步任務(wù)。
Tip : 共享資源可以是一個變量,一個從url下載圖片的任務(wù),讀取數(shù)據(jù)庫的任務(wù)等等。
一些理論知識(A Bit of Theory)
信號量是由一個線程隊列和一個計數(shù)器(Int類型)組成的。
線程隊列(Threads Queue) : 采用FIFO先進(jìn)先出機(jī)制用來追蹤等待線程(等待訪問共享資源的線程)。
計數(shù)器(Counter Value) : 用來決定一個線程是否可以訪問共享資源。(wait和signal方法都將改變計數(shù)器的值)
那么,我們什么時候調(diào)用wait和signal方法呢?
- 訪問共享資源之前調(diào)用
wait方法。wait方法可以理解為當(dāng)前線程在請求對共享資源的訪問,如果資源可用(未被其他線程占用)則允許訪問,否則等待(阻塞線程)。 - 訪問共享資源結(jié)束之后調(diào)用
signal方法。結(jié)束訪問時,要通知信號量,我用完了資源,讓別人(等待著的線程)來訪問吧。
調(diào)用wait方法時,行為規(guī)則如下:
- 計數(shù)器-1;
- 如果-1之后,當(dāng)前計數(shù)器小于0,則線程被阻塞;
- 如果-1之后,當(dāng)前計數(shù)器大于等于0,則線程被放行,無需等待;
調(diào)用signal方法時,行為規(guī)則如下:
- 計數(shù)器+1;
- 如果+1之前,計數(shù)器小于0,此方法將從線程隊列中按照FIFO規(guī)則提取第一個等待中的線程并喚醒。
- 如果+1之前,計數(shù)器大于等于0,說明線程隊列是空的,沒有正在等待的線程。
上面所述流程可用圖示表示如下:

wait和signal執(zhí)行規(guī)則圖示
具體的例子可以查看上面文章中的例子(文章需要翻墻?。。?/p>
這里重點總結(jié)幾點實際使用過程中的小Tip:
-
wait方法有一個等待超時時間參數(shù)(尤其注意參數(shù)是.distantFuture(無限期等待下去)情況下不要在主線程中調(diào)用wait方法,可能將阻塞主線程。) - 調(diào)用
wait計數(shù)器即刻-1,等待結(jié)果為success時,計數(shù)器保持不變,由signal負(fù)責(zé)回歸計數(shù)器,但是如果等待結(jié)果是timedOut,計數(shù)器將自動+1回歸,因此,應(yīng)只在結(jié)果是success時才執(zhí)行signal操作。 - 構(gòu)造信號量時傳入的初始計數(shù)器數(shù)值,首先不允許為負(fù)值,其次為0時一般用于將異步任務(wù)轉(zhuǎn)成同步任務(wù),數(shù)值大于0時代表最多允許多少個線程同時訪問共享資源或者同時執(zhí)行多少個任務(wù)。
代碼示例:
-
異步任務(wù)轉(zhuǎn)同步任務(wù)
var value = 10 let semaphore = DispatchSemaphore(value: 0) var waitResult: DispatchTimeoutResult = .success DispatchQueue.global().async { value = 100 Thread.sleep(forTimeInterval: 3.0) if waitResult == .success { semaphore.signal() } } // 試著將這里的超時時間參數(shù)改改查看效果 waitResult = semaphore.wait(timeout: DispatchTime.distantFuture) switch waitResult { case .success: print("success") case .timedOut: print("timedOut") } 查看不羈閣:『GCD』詳盡總結(jié)的文章末尾,這里講到了信號量及其應(yīng)用示例。
了解一些概念:
優(yōu)先級反轉(zhuǎn)問題及解決方法