001@多用派发队列,少用同步锁
多用派發隊列,少用同步鎖
《Effective Objective-C》中第41條: 多用派發隊列,少用同步鎖。多個線程執行同一份代碼時,很可能會造成數據不同步,這是推薦采用GCD來為代碼加鎖解決問題。
鎖與線程安全
快速回顧一下鎖
鎖(lock) 或者 互斥鎖(mutex) 是一種結構,用來保證一段代碼在同一時刻只有一個線程執行。它們通常被用來保證多線程訪問同一可變數據結構時的數據一致性。主要有下面幾種鎖:
- 阻塞鎖(Blocking locks):常見的表現形式是當前線程會進入休眠,直到被其他線程釋放。
- 自旋鎖(Spinlocks):使用一個循環不斷地檢查鎖是否被釋放。如果等待情況很少話這種鎖是非常高效的,相反,等待情況非常多的情況下會浪費 CPU 時間。
- (Reader/writer locks):允許多個讀線程同時進入一段代碼,但當寫線程獲取鎖時,其他線程(包括讀取器)只能等待。這是非常有用的,因為大多數數據結構讀取時是線程安全的,但當其他線程邊讀邊寫時就不安全了。
- 遞歸鎖(Recursive locks):允許單個線程多次獲取相同的鎖。非遞歸鎖被同一線程重復獲取時可能會導致死鎖、崩潰或其他錯誤行為。
APIs
蘋果提供了一系列不同的鎖 API,下面列出了其中一些:
- pthread_mutex_t
- pthread_rwlock_t
- dispatch_queue_t
- NSOperationQueue 當配置為 serial 時
- NSLock
- OSSpinLock
除此之外,Objective-C 提供了 @synchronized 語法結構,它其實就是封裝了 pthread_mutex_t 。與其他 API 不同的是,@synchronized 并未使用專門的鎖對象,它可以將任意 Objective-C 對象視為鎖。@synchronized(someObject) 區域會阻止其他 @synchronized(someObject) 區域訪問同一對象指針。不同的 API 有不同的行為和能力:
- pthread_mutex_t 是一個可選擇性地配置為遞歸鎖的阻塞鎖;
- pthread_rwlock_t 是一個阻塞讀寫鎖;
- dispatch_queue_t 可以用作阻塞鎖,也可以通過使用 barrier block 配置一個同步隊列作為讀寫鎖,還支持異步執行加鎖代碼;
- NSOperationQueue 可以用作阻塞鎖。與 dispatch_queue_t 一樣,支持異步執行加鎖代碼。
- NSLock 是 Objective-C 類的阻塞鎖,它的同伴類 NSRecursiveLock 是遞歸鎖。
- OSSpinLock 顧名思義,是一個自旋鎖。 最后,@synchronized 是一個阻塞遞歸鎖。
OC中的方法鎖方法
互斥鎖塊@synchronized
// 1、創建一個鎖,等待塊執行完畢 -(vodi)synchronizedMethod {@synchronized(self) {//Safe} } 復制代碼NSLock對象
// 2、另外的一個方法時使用NSLock對象 _lock = [[NSLock alloc] init]; - (void)synchronizedMethod {[_lock lock]// Safe[_lock unlock] } 復制代碼互斥鎖pthread_mutex_lock
/// 3、互斥鎖 YYCache pthread_mutex_init(&_lock, NULL);- (NSUInteger)totalCost {pthread_mutex_lock(&_lock);NSUInteger totalCost = _lru->_totalCost;pthread_mutex_unlock(&_lock);return totalCost; }- (void)dealloc {...//銷毀互斥鎖pthread_mutex_destroy(&_lock); } 復制代碼信號量
_lock = dispatch_semaphore_create(1);復制代碼然后使用了宏來代替加鎖解鎖的代碼:
#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER) #define Unlock() dispatch_semaphore_signal(self->_lock) 復制代碼使用:
- (BOOL)containsObjectForKey:(NSString *)key {if (!key) return NO;Lock();BOOL contains = [_kv itemExistsForKey:key];Unlock();return contains; } 復制代碼濫用@synchronized會減低代碼效率,極端情況下回出現死鎖(deadlock)現象。也可以使用NSRecursiveLock這種遞歸鎖,這樣線程多次持有該鎖,而不會出現死鎖。
雖然加了鎖,但是沒法保證絕對的線程安全。因為一個線程多次調用獲取方法(getter),每次取到的結果未必相同。在兩次操作之間,可能會有其他線程寫入新的屬性值。
隊列
把設置操作與獲取操作都安排在序列化的隊列里面執行,這樣針對屬性的訪問操作就都同步了。
串行同步隊列
_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);//讀取字符串 - (NSString*)someString {__block NSString *localSomeString;dispatch_sync(_syncQueue, ^{localSomeString = _someString;});return localSomeString; }//設置字符串 - (void)setSomeString:(NSString*)someString {// 這里也可以優化成異步派發 dispatch_asyncdispatch_sync(_syncQueue, ^{_someString = someString;}); }復制代碼將同步與異步派發派發結合起來,可以實現與普通加鎖機制一樣的同步行為,這么做卻不會阻塞執行異步派發的的線程
并發隊列
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//讀取字符串 - (NSString*)someString {__block NSString *localSomeString;dispatch_sync(_syncQueue, ^{localSomeString = _someString;});return localSomeString; }//設置字符串 - (void)setSomeString:(NSString*)someString {// 柵欄函數dispatch_barrier_async(_syncQueue, ^{_someString = someString;}); }復制代碼同步隊列及柵欄塊,可以零同步行文更加高效
Swift中的應用
同步鎖
Swift中沒有*@synchronized* ,但是可以使用objc_sync_enter,這是@synchronized的底層實現
func synced(_ lock: Any, closure: () -> ()) {objc_sync_enter(lock)closure()objc_sync_exit(lock) }// 使用 synced(self) {println("This is a synchronized closure") }復制代碼也可以這樣進行分鐘:
// 也可以這樣使用 { objc_sync_enter(lock)defer { objc_sync_exit(lock) }//// code of critical section goes here// } // <-- 退出此塊時釋放鎖定// 如果我們加上異常的話,stackoverflow中有一個我覺得寫的比較好的方法: extension NSObject {func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows -> T{objc_sync_enter(lockObj)defer {objc_sync_exit(lockObj)}return try closure()} } 復制代碼使用效果如下:
class Foo: NSObject {func test() {print("1")objc_sync_enter(self)defer {objc_sync_exit(self)print("3")}print("2")} }class Foo2: Foo {override func test() {super.test()print("11")objc_sync_enter(self)defer {print("33")objc_sync_exit(self)}print("22")} }let test = Foo2() test.test() 復制代碼打印結果如下
1 2 3 11 22 33 復制代碼信號量
static private var syncSemaphores: [String: DispatchSemaphore] = [:]static func synced(_ lock: String, closure: () -> ()) {//get the semaphore or create itvar semaphore = syncSemaphores[lock]if semaphore == nil {semaphore = DispatchSemaphore(value: 1)syncSemaphores[lock] = semaphore}//lock semaphoresemaphore!.wait()//execute closureclosure()//unlock semaphoresemaphore!.signal()} 復制代碼并發隊列
方法與OC中是一樣的,在取值操作與賦值操作
public class UserData {private let myPropertyQueue = dispatch_queue_create("com.example.mygreatapp.property", DISPATCH_QUEUE_CONCURRENT)private var _myProperty = "" // Backing storagepublic var myProperty: String {get {var result = ""dispatch_sync(myPropertyQueue) {result = self._myProperty}return result}set {dispatch_barrier_async(myPropertyQueue) {self._myProperty = newValue}}} } 復制代碼總結
以上是生活随笔為你收集整理的001@多用派发队列,少用同步锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员是吃青春饭的吗?未来发展前途如何?
- 下一篇: 021:自定义path(或url)转换器