之前認爲信號量的初始值就是線程的最大併發數,不可更改的,其實並否則。swift
平時開發通常都使用GCD信號量(DispatchSemaphore)來解決線程安全問題:當多個線程訪問同一塊資源時,很容易引起數據錯亂和數據安全問題。安全
var ticketTotal = 15
let group: DispatchGroup = DispatchGroup()
// 賣票操做
func __saleTicket(_ saleCount: Int) {
DispatchQueue.global().async(group: group, qos: .default, flags: []) {
for _ in 0..<saleCount {
// 加個延時能夠大機率讓多條線程同時進行到這一步
sleep(1)
// 賣一張
self.ticketTotal -= 1
}
}
}
// 開始賣票
func startSaleTicket() {
print("\(Date()) 一開始總共有\(ticketTotal)張")
print("\(Date()) 第一次賣5張票")
__saleTicket(5)
print("\(Date()) 第二次賣5張票")
__saleTicket(5)
print("\(Date()) 第三次賣5張票")
__saleTicket(5)
group.notify(queue: .main) {
print("\(Date()) 理論上所有賣完了,實際上剩\(self.ticketTotal)張")
}
}
複製代碼
打印結果:多線程
func __saleTicket(_ saleCount: Int) {
DispatchQueue.global().async(group: group, qos: .default, flags: []) {
for _ in 0..<saleCount {
// 加個延時能夠大機率讓多條線程同時進行到這一步
sleep(1)
// 賣一張
self.semaphore.wait() // 加🔐
self.ticketTotal -= 1
self.semaphore.signal() // 解🔐
}
}
}
複製代碼
打印結果:併發
之前認爲信號量的初始值是指線程的最大併發數,不可更改的,直到看到其餘文章介紹的一個信號量用法:異步
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
func semaphoreTest() {
DispatchQueue.global().async {
DispatchQueue.main.async {
// 從主隊列中獲取一些信息
...
// 發送信號
self.semaphore.signal()
}
// 開始等待
self.semaphore.wait()
// 等待結束,線程繼續
}
}
複製代碼
看到這個用法就開始以爲奇怪了,明明初始化爲0,不就是線程最大併發數爲0嗎?不就是不能有線程能夠工做嗎?按道理應該會一直阻塞住這個子線程纔對,那這種用法有什麼意義呢?async
衆所周知,semaphore.wait()
是減1操做,不過這個減1操做的前提是信號量是否大於0:函數
semaphore.wait()
這句事後,纔會真正對信號量減1;而semaphore.signal()
是對信號量的加1操做,後來通過測試發現,經過semaphore.signal()
能夠任意添加信號量,因此初始化的信號量並不是不可更改的,是能夠隨意更改的。測試
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0) // 0
func semaphoreTest() {
semaphore.signal() // 0 + 1 = 1
semaphore.signal() // 1 + 1 = 2
semaphore.signal() // 2 + 1 = 3
semaphore.wait() // 3 - 1 = 2
print("\(Date()) \(Thread.current) hello_1")
semaphore.wait() // 2 - 1 = 1
print("\(Date()) \(Thread.current) hello_2")
semaphore.wait() // 1 - 1 = 0
print("\(Date()) \(Thread.current) hello_2")
// 延遲3秒去另外一個線程異步添加信號量
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
print("\(Date()) \(Thread.current) 信號量+1")
let result = self.semaphore.signal() // 0 + 1 = 1
print("\(Date()) \(Thread.current) result: \(result)");
/* * PS: signal() 會返回一個結果,文檔解釋爲: * This function returns non-zero if a thread is woken. Otherwise, zero is returned. 若是線程被喚醒,則此函數返回非零。不然,返回零。 * 這裏執行後會有一條線程被喚醒,因此返回1,前面的3次signal()返回的都是0,說明沒有線程被喚醒,不過信號量的確是有+1的。 */
}
semaphore.wait() // 等於0就」卡住「當前線程
print("\(Date()) \(Thread.current) hello_4") // 1 - 1 = 0
}
複製代碼
打印結果:ui
證實了信號量是能夠本身維護的,只是「看不見」(沒有API獲取)。spa
// 初始化信號量爲0,假設 semaphoreCount 是表明信號量的一個數字
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0) // semaphoreCount = 0
func semaphoreTest() {
DispatchQueue.global().async {
//【1】開始執行任務1
DispatchQueue.main.async {
//【4】開始執行任務2
...
//【5】任務2結束,信號量加1,發送信號,喚醒等待靠前的線程
self.semaphore.signal() // semaphoreCount + 1 = 1
}
//【2】任務1須要等待任務2執行完才繼續,判斷有無信號量
self.semaphore.wait() //【3】判斷信號量,發現 semaphoreCount == 0,這裏」卡住「(休眠)
//【6】能來到這裏,說明信號量至少爲1,喚醒了這條線程,同時對信號量減1
// semaphoreCount - 1 = 0
// 減1後若是等於0,那麼其餘還在等這個信號量的線程只能繼續等,而這條線程會繼續往下執行。
//【7】任務1繼續
}
}
複製代碼
semaphore.signal()
任意添加的,至關因而有個隱藏的semaphoreCount
來控制能有多少條線程能同時工做;semaphoreCount
至少要有 1 才能夠執行代碼,只要是 0,semaphore.wait()
就會讓線程休眠等着直到semaphoreCount
大於 0 才喚醒;semaphoreCount
,因此必定要注意:用過多少次semaphore.wait()
就記得也要用多少次semaphore.signal()
,保證使用配對,否則線程會永遠休眠。知道這些後,之後GCD信號量除了能夠加解🔐外,也能夠作到讓當前線程等待別的線程了,也就是說能夠控制線程的執行時機喔~
這些函數也是沒有相應API獲取次數,須要本身維護:
group.enter()
和 group.leave()
timer.resume()
和 timer.suspend()
suspend
狀態下執行cancel()
,不然會崩潰,因此記得在cancel()
前肯定 timer 是在運行resume
狀態下。