標籤:initial tar sspi 並且 就會 是你 ext 內容 訪問
線程鎖是什麼
在前面的文章中總結過多線程,總結了多線程之後,線程鎖也是必須要好好總結的東西,這篇文章構思的時候可能寫的東西得許多,只能擠時間一點點的慢慢的總結了,知道了線程之後要瞭解線程鎖就得先瞭解一下什麼是“線程鎖”。
“線程鎖”一段代碼在同一個時間內是只能被一個線程訪問的,為了避免在同一時間內有多個線程訪問同一段代碼就有了“鎖”的概念,比如說,線程A在訪問著一段代碼,進入這段代碼之後我們加了一個“鎖”。這個時候線程B又來訪問了,由於有了鎖線程B就會等待線程A訪問結束之後解開了“鎖”線程B就可以接著訪問這段代碼了,這樣就避免了在同一時間內多個線程訪問同一段代碼!
相信上面的解釋應該理解了“鎖”的概念,也知道它是為了什麼產生的,我們再舉一個例子,一個房子一個人(線程)進去之後就把門鎖上了,另一個人(線程)來了之後就在等待前面的人(線程)出來,等前面的人出來之後把門開啟,它才可以進入房間。這樣的解釋相信應該明白了“鎖”的概念,但是我們還是得強調一點,就是在這個“鎖”和“解鎖”之間不要有太多的“事”(執行代碼,也就是任務)做,不然會造成過長時間的等待!就是去了多線程真正的含義和所用!
下面我們一個個的來解釋我們常用的線程鎖。
NSLock
NSLock是最簡單的互斥鎖,下面的NSCondition、NSConditionLock以及NSRecursiveLock都是遵守了NSLocking協議的,我們就放在一起說,包括我們現在說的NSLock也是,我們看看這個NSLock裡面具體都有什麼,先看看它代碼裡面的方法:
public protocol NSLocking { public func lock() public func unlock()}open class NSLock : NSObject, NSLocking { open func `try`() -> Bool open func lock(before limit: Date) -> Bool @available(iOS 2.0, *) open var name: String?}
我們一個一個說說上面的方法:
1、lock和unlock 就是這個類最常用的兩個方法,“加鎖”和“解鎖”的方法。
2、try()方法是嘗試加鎖,失敗是不會阻塞線程的,如果擷取鎖失敗就不會執行加鎖代碼。
3、lock(before limit: Date) 這個方法是在後面參數傳入的時間內沒有擷取到線程鎖就阻塞線程,要是到期還沒有擷取到線程鎖就喚醒線程,這時候傳回值是NO。
下面是我們Demo中具體的使用的例子代碼:
var imageMutableArray:Array<Any> = Array.init() let lock = NSLock.init() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. for i in 0...1000 { imageMutableArray.append("imageArray==="+String(i)) } print("你初始化的數組個數是",imageMutableArray.count ) } // MARK: - startTestBtnAction override func removeFromDataImageArray() -> Void { // 我們使用多個線程去刪除一個數組裡面的東西,這樣就有一個資源競爭的問題,我們看看 // 你可以先把這裡的lock加鎖個解鎖的方法注釋掉,代碼會崩潰在imageMutableArray.removeFirst() // 關於這樣寫(不加鎖)時候的安全執行緒的問題 http://www.jianshu.com/p/2fce6a0bb17b while (true) { lock.lock() if (imageMutableArray.count > 0) { imageMutableArray.removeFirst() }else{ now = CFAbsoluteTimeGetCurrent() let resultString = "操作開始時間:" + String(describing: then) + "\n結束時間:"+String(describing: now) + "\n整個操作用時:"+String(now! - then!) + "ms" /* NOTE: 修改UI要在主線程,不能在子線程,剛開始疏忽報了下面的錯誤 */ DispatchQueue.main.async { self.resulttextView.text = resultString } return } lock.unlock() } }
NSCondition
NSCondition條件鎖,首先它也是遵循NSLocking協議的,這點和我們上面說的NSLock是一致的,所以它的加鎖和解鎖方式和我們前面說的NSLock是一樣的,就是lock和unlock方法,你要是簡單的使用它來解決線程同步的問題,那他簡單的用法和前面寫的NSLock也是一樣的。但我們要是把NSCondition當NSLock用那就真的是浪費了!NSCondition還有自己的wait和signal用法,這個和後面訊號量有點點類似,訊號量的我們下面再說,看看NSCondition部分的代碼:
// MARK: - startTestBtnAction override func removeFromDataImageArray() -> Void { while (true) { lock.lock() if (imageMutableArray.count > 0) { imageMutableArray.removeFirst() }else{ now = CFAbsoluteTimeGetCurrent() let resultString = "操作開始時間:" + String(describing: then) + "\n結束時間:"+String(describing: now) + "\n整個操作用時:"+String(now! - then!) + "ms" DispatchQueue.main.async { self.resulttextView.text = resultString } return } lock.unlock() } }
NSConditionLock
NSConditionLock同樣實現了NSLocking協議,不過測試一下之後你會發現這個新能比較低。NSConditionLock也能像NSCondition一樣能進行線程之間的等待調用,並且還是安全執行緒的。下面使我們Demo中的代碼,寫給這裡的只是最基本的加鎖解鎖的代碼,先看看:
var imageMutableArray:Array<Any> = Array.init() let lock = NSConditionLock.init() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. for i in 0...1000 { imageMutableArray.append("imageArray==="+String(i)) } print("NSCondition初始化的數組個數是",imageMutableArray.count ) } // MARK: - startTestBtnAction override func removeFromDataImageArray() -> Void { while (true) { lock.lock() if (imageMutableArray.count > 0) { imageMutableArray.removeFirst() }else{ now = CFAbsoluteTimeGetCurrent() let resultString = "操作開始時間:" + String(describing: then) + "\n結束時間:"+String(describing: now) + "\n整個操作用時:"+String(now! - then!) + "ms" DispatchQueue.main.async { self.resulttextView.text = resultString } return } lock.unlock() } }
NSRecursiveLock
有時候“加鎖代碼”中存在遞迴調用,遞迴開始前加鎖,遞迴調用開始後會重複執行此方法以至於反覆執行加鎖代碼最終造成死結,這個時候可以使用遞迴鎖來解決,也就是我們的NSRecursiveLock,它就是遞迴鎖!使用遞迴鎖可以在一個線程中反覆擷取鎖而不造成死結,在這個過程中也會記錄擷取鎖和釋放鎖的次數,只有等兩者平衡的時候才會釋放,下面是我們Demo中的樣本:
// 遞迴調用 func removeFromImageArray() -> Void { recursiveLock.lock() if (imageMutableArray.count>0) { imageMutableArray.removeFirst() self.removeFromImageArray() } recursiveLock.unlock() } // MARK: - removeFromDataImageArray // 模仿遞迴調用 override func removeFromDataImageArray() -> Void { let dispatchGroup = DispatchGroup.init() let dispatchQueue = DispatchQueue.init(label:queueLabel, qos: .default, attributes: .concurrent) dispatchQueue.async(group: dispatchGroup, qos: .default, flags: DispatchWorkItemFlags.noQoS) { self.removeFromImageArray() } dispatchGroup.notify(queue: DispatchQueue.main) { self.now = CFAbsoluteTimeGetCurrent() let resultString = "操作開始時間:" + String(describing: self.then) + "\n結束時間:"+String(describing: self.now) + "\n整個操作用時:"+String(self.now! - self.then!) + "ms" self.resulttextView.text = resultString } }
@synchronized
@synchronized你要說它簡單,它的用法的確都是比較簡單的,要想深了探究一下它的話,它裡面的東西還的確是挺多的!但我們是在Swift中來討論區對話鎖的,這裡也就不能再使用 @synchronized,因為在Swift中它是不在使用了的,相應代替它的是下面下面這兩句:objc_sync_enter() 中間是你需要加鎖的代碼 objc_sync_exit() ,那上面相同的操作我們用這個互斥鎖寫的話代碼如下:
// MARK: - removeFromDataImageArray override func removeFromDataImageArray() -> Void { while (true) { //互斥鎖 objc_sync_enter(self) if (imageMutableArray.count > 0) { imageMutableArray.removeFirst() }else{ now = CFAbsoluteTimeGetCurrent() let resultString = "操作開始時間:" + String(describing: then) + "\n結束時間:"+String(describing: now) + "\n整個操作用時:"+String(now! - then!) + "ms" DispatchQueue.main.async { self.resulttextView.text = resultString } return } objc_sync_exit(self) } }
dispatch_semaphore_t 訊號量
dispatch_semaphore_t是屬於GCD裡面的東西,在前面終結多線程的時候我們說把它放在我們總結線程鎖的時候說,在這裡我們就說一些這個訊號量,dispatch_semaphore_t 和前面@synchronized一樣都是我們OC的寫法,在我們的Swift中也不是這樣寫的,全部的內容都是在DispatchSemaphore中,關於GCD方面API的對比我們在下面做了一張表,大致的說一下:
你看完了這張圖的對比以及總結之後,我們說回我們的主題:DispatchSemaphore 可以看到它的主要的方法:
open class DispatchSemaphore : DispatchObject {}/// dispatch_semaphoreextension DispatchSemaphore { // 發送訊號,讓訊號量+1方法 public func signal() -> Int // 等待,讓訊號量-1方法 public func wait() // 下面兩個方法可以設定等待的時間,過了這個時間要是沒有讓訊號量大於或者等於初始化的訊號量值的時候 // 就會自己接著往執行代碼,相等於我們的鎖是失效了的 public func wait(timeout: DispatchTime) -> DispatchTimeoutResult public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult}extension DispatchSemaphore { /*! * @function dispatch_semaphore_create * * @abstract * Creates new counting semaphore with an initial value. * * @discussion * Passing zero for the value is useful for when two threads need to reconcile * the completion of a particular event. Passing a value greater than zero is * useful for managing a finite pool of resources, where the pool size is equal * to the value. * * @param value * The starting value for the semaphore. Passing a value less than zero will * cause NULL to be returned. * * @result * The newly created semaphore, or NULL on failure. */ @available(iOS 4.0, *) public /*not inherited*/ init(value: Int)}
OC和Swift的用法是一樣的,只是在寫法上有一些的區別,這裡就不再說OC的了,我們直接看看Swift的代碼怎麼寫:
// MARK: - startTestBtnAction override func removeFromDataImageArray() -> Void { while (true) { /* 也可以直接寫: semaPhore.wait() 這裡發生一個等待訊號,訊號量就-1,變成0 ,後面的任務就會處於等待狀態, 等到訊號量大於等於1的時候在執行,要是訊號量不大於或者等於你初始化時候的值,它就一直處於等待狀態 當然,你也可以在這裡這是等待的時間 semaPhore.wait(timeout: DispatchTime.init(uptimeNanoseconds: UInt64(10))) 但過了這個時間之後在進入就等於是我們的鎖失效了。面臨的問題也就是相應的崩潰,在刪除方法哪裡,可以自己試一下 */ _ = semaPhore.wait(timeout: DispatchTime.distantFuture) if (imageMutableArray.count > 0) { imageMutableArray.removeFirst() }else{ now = CFAbsoluteTimeGetCurrent() let resultString = "操作開始時間:" + String(describing: then) + "\n結束時間:"+String(describing: now) + "\n整個操作用時:"+String(now! - then!) + "ms" DispatchQueue.main.async { self.resulttextView.text = resultString } // 不要忘記在這裡加處理,不然return之後是執行不到下面的semaPhore.signal()代碼 semaPhore.signal() return } // signal() 方法,這裡會使訊號量+1 semaPhore.signal() } }
POSIX
POSIX和我們前面寫的dispatch_semaphore_t用法是挺像的,但探究本質的haul它們根本就不是一個東西,POSIX是Unix/Linux平台上提供的一套條件互斥鎖的API。你要是在OC的檔案中只用的話你需要匯入標頭檔:pthread.h
在Swift中就不用了,但是在使用的時候不管是OC的還是Swift的,代碼是一致的,它的幾個主要的方法就是下面三個,剩下的具體的代碼可以看demo或者是下面基本的方法:
1、pthread_mutex_init 初始化方法
2、pthread_mutex_lock 加鎖方法
3、pthread_mutex_unlock 解鎖方法
class POSIXController: ThreadLockController { var imageMutableArray:Array<Any> = Array.init() var mutex:pthread_mutex_t = pthread_mutex_t() //初始化pthread_mutex_t類型變數 override func viewDidLoad() { super.viewDidLoad() // 初始化 pthread_mutex_init(&mutex,nil) // Do any additional setup after loading the view. for i in 0...1000 { imageMutableArray.append("imageArray==="+String(i)) } print("NSLock初始化的數組個數是",imageMutableArray.count) } // MARK: - startTestBtnAction override func removeFromDataImageArray() -> Void { while (true) { // 加鎖 pthread_mutex_lock(&mutex) if (imageMutableArray.count > 0) { imageMutableArray.removeFirst() }else{ now = CFAbsoluteTimeGetCurrent() let resultString = "操作開始時間:" + String(describing: then) + "\n結束時間:"+String(describing: now) + "\n整個操作用時:"+String(now! - then!) + "ms" DispatchQueue.main.async { self.resulttextView.text = resultString } pthread_mutex_unlock(&mutex); return } // 解鎖 pthread_mutex_unlock(&mutex) } } /* Swift 的deinit函數實際的作用和OC中的dealloc函數是一樣的 對象的釋放 通知 代理等等的處理都是在這裡處理的 */ deinit { pthread_mutex_destroy(&mutex); //釋放該鎖的資料結構 }}
剩下的還有什麼
1、OSSpinLock
首先要提的是OSSpinLock已經出現了BUG,導致並不能完全保證是安全執行緒,所以這個我們知道,大概的瞭解一下,具體的問題可以去這裡仔細看看:不再安全的 OSSpinLock
2、dispatch_barrier_async/dispatch_barrier_sync
這個我在前面總結GCD的時候說過了這個“柵欄”函數,就不在這裡重複說了
3、最後就是Demo的地址了,這個Demo原本是想用Swift試著模仿一下的UI的,包括聊天架構那部分,以前寫過OC的,這次春被用Swift寫一下,主要也是為了用一下Swift,以及看一下4.0它的一些新的特性,不然很久不寫,一些東西比較容易遺忘!
DemoGit地址
iOS 多線程之線程鎖Swift-Demo樣本總結