线程安全: 互斥锁和自旋锁(10种)
無并發(fā),不編程.提到多線程就很難繞開鎖?.
iOS開發(fā)中較常見的兩類鎖:
1. 互斥鎖: 同一時刻只能有一個線程獲得互斥鎖,其余線程處于掛起狀態(tài).
2. 自旋鎖: 當某個線程獲得自旋鎖后,別的線程會一直做循環(huán),嘗試加鎖,當超過了限定的次數(shù)仍然沒有成功獲得鎖時,線程也會被掛起.
自旋鎖較適用于鎖的持有者保存時間較短的情況下,實際使用中互斥鎖會用的多一些.
1. 互斥鎖,信號量
1.遵守NSLocking協(xié)議的四種鎖
四種鎖分別是:
NSLock、NSConditionLock、NSRecursiveLock、NSCondition
NSLocking協(xié)議
public protocol NSLocking { public func lock()public func unlock() } 復(fù)制代碼下面舉個多個售票點同時賣票的例子
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() } private func saleTickets() {while true {lock.lock()Thread.sleep(forTimeInterval: 0.5) // 模擬延遲if ticket > 0 {ticket = ticket - 1print("\(String(describing: Thread.current.name!)) 賣出了一張票,當前還剩\(ticket)張票")lock.unlock()}else {print("oh 票已經(jīng)賣完了")lock.unlock()break;}} } 復(fù)制代碼遵守協(xié)議后實現(xiàn)的兩個方法lock()和unlock(),意如其名.
除此之外NSLock、NSConditionLock、NSRecursiveLock、NSCondition四種互斥鎖各有其實現(xiàn):
1. 除NSCondition外,三種鎖都有的兩個方法:
// 嘗試去鎖,如果成功,返回true,否則返回falseopen func `try`() -> Bool// 在limit時間之前獲得鎖,沒有返回NOopen func lock(before limit: Date) -> Bool 復(fù)制代碼2. NSCondition條件鎖:
// 當前線程掛起open func wait()// 當前線程掛起,設(shè)置一個喚醒時間open func wait(until limit: Date) -> Bool// 喚醒在等待的線程open func signal()// 喚醒所有NSCondition掛起的線程open func broadcast() 復(fù)制代碼當調(diào)用wait()之后,NSCondition實例會解鎖已有鎖的當前線程,然后再使線程休眠,當被signal()通知后,線程被喚醒,然后再給當前線程加鎖,所以看起來好像wait()一直持有該鎖,但根據(jù)蘋果文檔中說明,直接把wait()當線程鎖并不能保證線程安全.
3. NSConditionLock條件鎖:
NSConditionLock是借助NSCondition來實現(xiàn)的,在NSCondition的基礎(chǔ)上加了限定條件,可自定義程度相對NSCondition會高些.
// 鎖的時候還需要滿足conditionopen func lock(whenCondition condition: Int)// 同try,同樣需要滿足conditionopen func tryLock(whenCondition condition: Int) -> Bool// 同unlock,需要滿足conditionopen func unlock(withCondition condition: Int)// 同lock,需要滿足condition和在limit時間之前open func lock(whenCondition condition: Int, before limit: Date) -> Bool 復(fù)制代碼4. NSRecurisiveLock遞歸鎖:
定義了可以多次給相同線程上鎖并不會造成死鎖的鎖.
提供的幾個方法和NSLock類似.
2. GCD的DispatchSemaphore和柵欄函數(shù)
1. DispatchSemaphore信號量:
DispatchSemaphore中的信號量,可以解決資源搶占的問題,支持信號的通知和等待.每當發(fā)送一個信號通知,則信號量+1;每當發(fā)送一個等待信號時信號量-1,如果信號量為0則信號會處于等待狀態(tài).直到信號量大于0開始執(zhí)行.所以我們一般將DispatchSemaphore的value設(shè)置為1.
下面給出了DispatchSemaphore的封裝類
class GCDSemaphore {// MARK: 變量fileprivate var dispatchSemaphore: DispatchSemaphore!// MARK: 初始化public init() {dispatchSemaphore = DispatchSemaphore(value: 0)}public init(withValue: Int) {dispatchSemaphore = DispatchSemaphore(value: withValue)}// 執(zhí)行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}} } 復(fù)制代碼2. barrier柵欄函數(shù):
柵欄函數(shù)也可以做線程同步,當然了這個肯定是要并行隊列中才能起作用.只有當當前的并行隊列執(zhí)行完畢,才會執(zhí)行柵欄隊列.
/// 創(chuàng)建并發(fā)隊列 let queue = DispatchQueue(label: "queuename", attributes: .concurrent) /// 異步函數(shù) queue.async {for _ in 1...5 {print(Thread.current)} } queue.async {for _ in 1...5 {print(Thread.current)} } /// 柵欄函數(shù) queue.async(flags: .barrier) {print("barrier") } queue.async {for _ in 1...5 {print(Thread.current)} } 復(fù)制代碼3. 其他的互斥鎖
1. pthread_mutex互斥鎖
pthread表示POSIX thread,跨平臺的線程相關(guān)的API,pthread_mutex也是一種互斥鎖,互斥鎖的實現(xiàn)原理與信號量非常相似,阻塞線程并睡眠,需要進行上下文切換.
一般情況下,一個線程只能申請一次鎖,也只能在獲得鎖的情況下才能釋放鎖,多次申請鎖或釋放未獲得的鎖都會導(dǎo)致崩潰.假設(shè)在已經(jīng)獲得鎖的情況下再次申請鎖,線程會因為等待鎖的釋放而進入睡眠狀態(tài),因此就不可能再釋放鎖,從而導(dǎo)致死鎖.
這邊給出了一個基于pthread_mutex_t(安全的"FIFO"互斥鎖)的封裝 MutexLock
1. @synchronized條件鎖
日常開發(fā)中最常用的應(yīng)該是@synchronized,這個關(guān)鍵字可以用來修飾一個變量,并為其自動加上和解除互斥鎖.這樣,可以保證變量在作用范圍內(nèi)不會被其他線程改變.但是在swift中它已經(jīng)不存在了.其實@synchronized在幕后做的事情是調(diào)用了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不會被其他線程改變 }復(fù)制代碼2. 自旋鎖
1. OSSpinLock自旋鎖
OSSpinLock是執(zhí)行效率最高的鎖,不過在iOS10.0以后已經(jīng)被廢棄了.
詳見大神ibireme的不再安全的 OSSpinLock
2. os_unfair_lock自旋鎖
它能夠保證不同優(yōu)先級的線程申請鎖的時候不會發(fā)生優(yōu)先級反轉(zhuǎn)問題.這是蘋果為了取代OSSPinLock新出的一個能夠避免優(yōu)先級帶來的死鎖問題的一個鎖,OSSPinLock就是有由于優(yōu)先級造成死鎖的問題.
注意: 這個鎖適用于小場景下的一個高效鎖,否則會大量消耗cpu資源.
var unsafeMutex = os_unfair_lock() os_unfair_lock_lock(&unsafeMutex) os_unfair_lock_trylock(&unsafeMutex) os_unfair_lock_unlock(&unsafeMutex) 復(fù)制代碼這邊給出了基于os_unfair_lock的封裝 MutexLock
3. 性能比較
這邊貼一張大神ibireme在iPhone6、iOS9對各種鎖的性能測試圖
本文收錄于 SwiftTips
參考:
不再安全的OSSpinLock
深入理解iOS開發(fā)中的鎖
如有疑問,歡迎留言 :-D
總結(jié)
以上是生活随笔為你收集整理的线程安全: 互斥锁和自旋锁(10种)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows上的原生Linux容器(盆
- 下一篇: 怎么将织梦图集模型编辑器改为文章编辑器?