無并發,不程式設計.提到多線程就很難繞開鎖.
iOS開發中較常見的兩類鎖:
1. 互斥鎖: 同一時刻隻能有一個線程獲得互斥鎖,其餘線程處于挂起狀态.
2. 自旋鎖: 當某個線程獲得自旋鎖後,别的線程會一直做循環,嘗試加鎖,當超過了限定的次數仍然沒有成功獲得鎖時,線程也會被挂起.
自旋鎖較适用于鎖的持有者儲存時間較短的情況下,實際使用中互斥鎖會用的多一些.
1. 互斥鎖,信号量
1.遵守NSLocking協定的四種鎖
四種鎖分别是:
NSLock、NSConditionLock、NSRecursiveLock、NSCondition
NSLocking協定public protocol NSLocking {
public func lock()
public func unlock()
}
下面舉個多個售票點同時賣票的例子var ticket = 20
var lock = NSLock()
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
let thread1 = Thread(target: self, selector: #selector(saleTickets), object: nil)
thread1.name = "售票點A"
thread1.start()
let thread2 = Thread(target: self, selector: #selector(saleTickets), object: nil)
thread2.name = "售票點B"
thread2.start()
}
@objc private func saleTickets() {
while true {
lock.lock()
Thread.sleep(forTimeInterval: 0.5) // 模拟延遲
if ticket > 0 {
ticket = ticket - 1
print("\(String(describing: Thread.current.name!)) 賣出了一張票,目前還剩\(ticket)張票")
lock.unlock()
}else {
print("oh 票已經賣完了")
lock.unlock()
break;
}
}
}
遵守協定後實作的兩個方法lock()和unlock(),意如其名.
除此之外NSLock、NSConditionLock、NSRecursiveLock、NSCondition四種互斥鎖各有其實作:
1. 除NSCondition外,三種鎖都有的兩個方法:// 嘗試去鎖,如果成功,傳回true,否則傳回false
open func `try`() -> Bool
// 在limit時間之前獲得鎖,沒有傳回NO
open func lock(before limit: Date) -> Bool
2. NSCondition條件鎖:// 目前線程挂起
open func wait()
// 目前線程挂起,設定一個喚醒時間
open func wait(until limit: Date) -> Bool
// 喚醒在等待的線程
open func signal()
// 喚醒所有NSCondition挂起的線程
open func broadcast()
當調用wait()之後,NSCondition執行個體會解鎖已有鎖的目前線程,然後再使線程休眠,當被signal()通知後,線程被喚醒,然後再給目前線程加鎖,是以看起來好像wait()一直持有該鎖,但根據蘋果文檔中說明,直接把wait()當線程鎖并不能保證線程安全.
3. NSConditionLock條件鎖:
NSConditionLock是借助NSCondition來實作的,在NSCondition的基礎上加了限定條件,可自定義程度相對NSCondition會高些.// 鎖的時候還需要滿足condition
open func lock(whenCondition condition: Int)
// 同try,同樣需要滿足condition
open func tryLock(whenCondition condition: Int) -> Bool
// 同unlock,需要滿足condition
open func unlock(withCondition condition: Int)
// 同lock,需要滿足condition和在limit時間之前
open func lock(whenCondition condition: Int, before limit: Date) -> Bool
4. NSRecurisiveLock遞歸鎖:
定義了可以多次給相同線程上鎖并不會造成死鎖的鎖.
提供的幾個方法和NSLock類似.
2. GCD的DispatchSemaphore和栅欄函數
1. DispatchSemaphore信号量:
DispatchSemaphore中的信号量,可以解決資源搶占的問題,支援信号的通知和等待.每當發送一個信号通知,則信号量+1;每當發送一個等待信号時信号量-1,如果信号量為0則信号會處于等待狀态.直到信号量大于0開始執行.是以我們一般将DispatchSemaphore的value設定為1.
下面給出了DispatchSemaphore的封裝類class GCDSemaphore {
// MARK: 變量
fileprivate var dispatchSemaphore: DispatchSemaphore!
// MARK: 初始化
public init() {
dispatchSemaphore = DispatchSemaphore(value: 0)
}
public init(withValue: Int) {
dispatchSemaphore = DispatchSemaphore(value: withValue)
}
// 執行
public func signal() -> Bool {
return dispatchSemaphore.signal() != 0
}
public func wait() {
_ = dispatchSemaphore.wait(timeout: DispatchTime.distantFuture)
}
public func wait(timeoutNanoseconds: DispatchTimeInterval) -> Bool {
if dispatchSemaphore.wait(timeout: DispatchTime.now() + timeoutNanoseconds) == DispatchTimeoutResult.success {
return true
} else {
return false
}
}
}
2. barrier栅欄函數:
栅欄函數也可以做線程同步,當然了這個肯定是要并行隊列中才能起作用.隻有當目前的并行隊列執行完畢,才會執行栅欄隊列./// 建立并發隊列
let queue = DispatchQueue(label: "queuename", attributes: .concurrent)
/// 異步函數
queue.async {
for _ in 1...5 {
print(Thread.current)
}
}
queue.async {
for _ in 1...5 {
print(Thread.current)
}
}
/// 栅欄函數
queue.async(flags: .barrier) {
print("barrier")
}
queue.async {
for _ in 1...5 {
print(Thread.current)
}
}
3. 其他的互斥鎖
1. pthread_mutex互斥鎖
pthread表示POSIX thread,跨平台的線程相關的API,pthread_mutex也是一種互斥鎖,互斥鎖的實作原理與信号量非常相似,阻塞線程并睡眠,需要進行上下文切換.
一般情況下,一個線程隻能申請一次鎖,也隻能在獲得鎖的情況下才能釋放鎖,多次申請鎖或釋放未獲得的鎖都會導緻崩潰.假設在已經獲得鎖的情況下再次申請鎖,線程會因為等待鎖的釋放而進入睡眠狀态,是以就不可能再釋放鎖,進而導緻死鎖.
這邊給出了一個基于pthread_mutex_t(安全的"FIFO"互斥鎖)的基本封裝 MutexLock
1. @synchronized條件鎖
日常開發中最常用的應該是@synchronized,這個關鍵字可以用來修飾一個變量,并為其自動加上和解除互斥鎖.這樣,可以保證變量在作用範圍内不會被其他線程改變.但是在swift中它已經不存在了.其實@synchronized在幕後做的事情是調用了objc_sync中的objc_sync_enter和objc_sync_exit 方法,并且加入了一些異常判斷.
是以我們可以利用閉包自己封裝一套.func synchronized(lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
// 使用
synchronized(lock: AnyObject) {
// 此處AnyObject不會被其他線程改變
}
2. 自旋鎖
1. OSSpinLock自旋鎖
執行效率最高的鎖,不過在iOS10.0以後廢棄了這種鎖機制,使用os_unfair_lock替換,
顧名思義能夠保證不同優先級的線程申請鎖的時候不會發生優先級反轉問題.
2. os_unfair_lock自旋鎖
對于os_unfair_lock,非FIFO的鎖,蘋果為了取代OSSPinLock新出的一個鎖,一個進階的能夠避免優先級帶來的死鎖問題的一個鎖,OSSPinLock之前就爆出在iOS平台有由于優先級造成死鎖的問題).
注意: 這個鎖适用于小場景下的一個高效鎖,否則會大量消耗cpu資源.var unsafeMutex = os_unfair_lock()
os_unfair_lock_lock(&unsafeMutex)
os_unfair_lock_trylock(&unsafeMutex)
os_unfair_lock_unlock(&unsafeMutex)
這邊給出了基于os_unfair_lock的封裝 MutexLock
3. 性能比較
這邊貼一張大神ibireme在iPhone6、iOS9對各種鎖的性能測試圖
作者:Dariel
連結:https://www.jianshu.com/p/a321377c5639