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