無併發,不編程.提到多線程就很難繞開鎖🔐.ios
iOS開發中較常見的兩類鎖:git
自旋鎖較適用於鎖的持有者保存時間較短的狀況下,實際使用中互斥鎖會用的多一些.github
NSLocking
協議的四種鎖四種鎖分別是:
NSLock
、NSConditionLock
、NSRecursiveLock
、NSCondition
編程
NSLocking
協議swift
public protocol NSLocking {
public func lock()
public func unlock()
}
複製代碼
下面舉個多個售票點同時賣票的例子安全
var ticket = 20
var lock = NSLock()
override func touchesBegan(_ touches: Set<UITouch>, 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
四種互斥鎖各有其實現:閉包
NSCondition
外,三種鎖都有的兩個方法:// 嘗試去鎖,若是成功,返回true,不然返回false
open func `try`() -> Bool
// 在limit時間以前得到鎖,沒有返回NO
open func lock(before limit: Date) -> Bool
複製代碼
NSCondition
條件鎖:// 當前線程掛起
open func wait()
// 當前線程掛起,設置一個喚醒時間
open func wait(until limit: Date) -> Bool
// 喚醒在等待的線程
open func signal()
// 喚醒全部NSCondition掛起的線程
open func broadcast()
複製代碼
當調用wait()
以後,NSCondition
實例會解鎖已有鎖的當前線程,而後再使線程休眠,當被signal()
通知後,線程被喚醒,而後再給當前線程加鎖,因此看起來好像wait()
一直持有該鎖,但根據蘋果文檔中說明,直接把wait()
當線程鎖並不能保證線程安全.併發
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
複製代碼
NSRecurisiveLock
遞歸鎖:定義了能夠屢次給相同線程上鎖並不會形成死鎖的鎖.
提供的幾個方法和NSLock
相似.
DispatchSemaphore
和柵欄函數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
}
}
}
複製代碼
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)
}
}
複製代碼
pthread_mutex
互斥鎖pthread
表示POSIX thread
,跨平臺的線程相關的API,pthread_mutex
也是一種互斥鎖,互斥鎖的實現原理與信號量很是類似,阻塞線程並睡眠,須要進行上下文切換.
通常狀況下,一個線程只能申請一次鎖,也只能在得到鎖的狀況下才能釋放鎖,屢次申請鎖或釋放未得到的鎖都會致使崩潰.假設在已經得到鎖的狀況下再次申請鎖,線程會由於等待鎖的釋放而進入睡眠狀態,所以就不可能再釋放鎖,從而致使死鎖.
這邊給出了一個基於pthread_mutex_t
(安全的"FIFO"互斥鎖)的封裝 MutexLock
平常開發中最經常使用的應該是@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不會被其餘線程改變
}
複製代碼
OSSpinLock
自旋鎖OSSpinLock
是執行效率最高的鎖,不過在iOS10.0之後已經被廢棄了.
詳見大神ibireme的再也不安全的 OSSpinLock
os_unfair_lock
自旋鎖它可以保證不一樣優先級的線程申請鎖的時候不會發生優先級反轉問題.這是蘋果爲了取代OSSPinLock
新出的一個可以避免優先級帶來的死鎖問題的一個鎖,OSSPinLock
就是有因爲優先級形成死鎖的問題.
注意: 這個鎖適用於小場景下的一個高效鎖,不然會大量消耗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
這邊貼一張大神ibireme在iPhone六、iOS9對各類鎖的性能測試圖
參考:
再也不安全的OSSpinLock
深刻理解iOS開發中的鎖
若有疑問,歡迎留言 :-D