線程安全: 互斥鎖和自旋鎖(10種)

無併發,不編程.提到多線程就很難繞開鎖🔐.ios

iOS開發中較常見的兩類鎖:git

1. 互斥鎖: 同一時刻只能有一個線程得到互斥鎖,其他線程處於掛起狀態.
2. 自旋鎖: 當某個線程得到自旋鎖後,別的線程會一直作循環,嘗試加鎖,當超過了限定的次數仍然沒有成功得到鎖時,線程也會被掛起.

自旋鎖較適用於鎖的持有者保存時間較短的狀況下,實際使用中互斥鎖會用的多一些.github

1. 互斥鎖,信號量

1.遵照NSLocking協議的四種鎖

四種鎖分別是:
NSLockNSConditionLockNSRecursiveLockNSCondition編程

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(),意如其名.多線程

除此以外NSLockNSConditionLockNSRecursiveLockNSCondition四種互斥鎖各有其實現:閉包

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_enterobjc_sync_exit 方法,而且加入了一些異常判斷.

所以咱們能夠利用閉包本身封裝一套.

func synchronized(lock: AnyObject, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

// 使用
synchronized(lock: AnyObject) {
    // 此處AnyObject不會被其餘線程改變
}

複製代碼

2. 自旋鎖

1. OSSpinLock自旋鎖

OSSpinLock是執行效率最高的鎖,不過在iOS10.0之後已經被廢棄了.

詳見大神ibireme的再也不安全的 OSSpinLock

2. 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

3. 性能比較

這邊貼一張大神ibireme在iPhone六、iOS9對各類鎖的性能測試圖

本文收錄於 SwiftTips

參考:
再也不安全的OSSpinLock
深刻理解iOS開發中的鎖

若有疑問,歡迎留言 :-D

相關文章
相關標籤/搜索