iOS開(kāi)發(fā)常用線程安全鎖
正文
多線程開(kāi)發(fā),就會(huì)有資源搶占的情況,導(dǎo)致出現(xiàn)我們意想不到的數(shù)據(jù)問(wèn)題,我們就需要對(duì)數(shù)據(jù)進(jìn)行加鎖,已保證線程安全.
鎖主要分為兩大類(lèi)自旋鎖和互斥鎖。
- 自旋鎖:自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,因此是一種忙等待。自旋鎖避免了線程上下文切換的調(diào)度開(kāi)銷(xiāo),因此對(duì)于線程只會(huì)阻塞很短的時(shí)間是很高效的,但是對(duì)于比較長(zhǎng)時(shí)間的阻塞也是比較消耗CPU的。(線程忙等)
- 互斥鎖:如果資源已經(jīng)被占用,資源申請(qǐng)者只能進(jìn)入睡眠狀態(tài)。有上下文的切換(主動(dòng)出讓時(shí)間片, 線程休眠, 等待下一次喚醒)、CPU的搶占、信號(hào)的發(fā)送等開(kāi)銷(xiāo)。(線程閑等)
原子屬性
我們創(chuàng)建屬性一般都會(huì)設(shè)置屬性為非原子屬性noatomic, 因?yàn)樵訉傩詀tomic會(huì)有額外的加鎖開(kāi)銷(xiāo),那如果我們創(chuàng)建屬性使用原子屬性atomic,它能保證property是線程安全的嗎?
#import "ViewController.h" @interface ViewController () @property (atomic ,assign) int count; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.count = 0; [self test_atomic]; } - (void)test_atomic { // self.count初始值是10 for (int i = 0; i < 10; i ++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ self.count ++; NSLog(@"%d",self.count); }); } } @end
從上面我們可以看到原子屬性atomic不能保證數(shù)據(jù)的線程安全.下面我們從源碼進(jìn)行分析:在屬性的getter/setter方法調(diào)用的底層atomic和nonatomic有什么區(qū)別。先看看setter方法:objc_setProperty
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY); bool mutableCopy = (shouldCopy == MUTABLE_COPY); reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy); } void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset{ reallySetProperty(self, _cmd, newValue, offset, true, false, false); } void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, false, false, false); } void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, true, true, false); } void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset, false, true, false); }
我們可以看到都是調(diào)用的reallySetProperty方法,atomic第五個(gè)參數(shù)為true,nonatomic為false, copy第六個(gè)參數(shù)為true, mutableCopy第七個(gè)參數(shù)為true.
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) { if (offset == 0) { object_setClass(self, newValue); return; } id oldValue; id *slot = (id*) ((char*)self + offset); if (copy) { newValue = [newValue copyWithZone:nil]; } else if (mutableCopy) { newValue = [newValue mutableCopyWithZone:nil]; } else { if (*slot == newValue) return; newValue = objc_retain(newValue); } if (!atomic) { oldValue = *slot; *slot = newValue; } else { spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } objc_release(oldValue); }
copy和mutableCopy使用copyWithZone進(jìn)行新值的copy,其他使用objc_retain增加引用計(jì)數(shù).nonatomic直接進(jìn)行賦值;atomic會(huì)使用spinlock_t在賦值之前加鎖,賦值之后解鎖. 我們?cè)賮?lái)看看getter方法:objc_getProperty
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) { if (offset == 0) { return object_getClass(self); } // Retain release world id *slot = (id*) ((char*)self + offset); if (!atomic) return *slot; // Atomic retain release world spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); id value = objc_retain(*slot); slotlock.unlock(); // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock. return objc_autoreleaseReturnValue(value); }
我們可以看到nonatomic直接返回,atomic在取值前加鎖,取值后解鎖,再返回值.
那么原子屬性atomic在getter/setter底層有加鎖解鎖操作,為什么不能保證線程安全的呢?
因?yàn)樵訉傩詀tomic鎖住資源的范圍不夠大。在self.count --;的時(shí)候,既有g(shù)etter也有setter,可能就出現(xiàn)當(dāng)getter的時(shí)候還沒(méi)有return出去就被其它線程setter。
OSSpinLock - 自旋鎖
OSSpinLock 在iOS10之后被移除了。 被移除的原因是它有一個(gè)bug:優(yōu)先級(jí)反轉(zhuǎn)。
優(yōu)先級(jí)反轉(zhuǎn):當(dāng)多個(gè)線程有優(yōu)先級(jí)的時(shí)候,有一個(gè)優(yōu)先級(jí)較低的線程先去訪問(wèn)了資源,并是有了OSSpinLock對(duì)資源加鎖,又來(lái)一個(gè)優(yōu)先級(jí)較高的線程去訪問(wèn)了這個(gè)資源,這個(gè)時(shí)候優(yōu)先級(jí)較高的線程就會(huì)一直占用cpu的資源,導(dǎo)致優(yōu)先級(jí)較低的線程沒(méi)辦法與較高的線程爭(zhēng)奪cpu的時(shí)間,最后導(dǎo)致最先被優(yōu)先級(jí)較低的線程鎖住的資源遲遲不能被釋放,從而造成優(yōu)先級(jí)反轉(zhuǎn)的bug。
所以 OSSpinLock使用限制:必須保證所有訪問(wèn)同一資源的線程處于優(yōu)先級(jí)平等的時(shí)候,才可以使用。
OSSpinLock已被蘋(píng)果放棄了,大家也可以放棄它,蘋(píng)果設(shè)計(jì)了os_unfair_lock來(lái)代替OSSpinLock。
os_unfair_lock - 互斥鎖
iOS10之后開(kāi)始支持,os_unfair_lock 在os庫(kù)中,使用之前需要導(dǎo)入頭文件<os/lock.h>。
#import "ViewController.h" #import <os/lock.h> @interface ViewController () @property (nonatomic ,assign) int count; @property (nonatomic ,assign) os_unfair_lock unfairLock; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.count = 0; self.unfairLock = OS_UNFAIR_LOCK_INIT; // 初始化鎖 } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (int i = 0; i<10; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ os_unfair_lock_lock(&_unfairLock); // 加鎖 self.count ++; NSLog(@"%d",self.count); os_unfair_lock_unlock(&_unfairLock); // 解鎖 }); } } @end
NSLock - 互斥鎖
NSLock - Foundation框架內(nèi)部的??,使用起來(lái)非常方便,基于pthroad_mutex封裝而來(lái),是一把互斥非遞歸鎖。因?yàn)镺C的Foundation框架是非開(kāi)源的,所以我們查看swift的Foundation框架,來(lái)查看其源碼實(shí)現(xiàn),原理是相同的
#if os(Windows) #elseif CYGWIN #else private typealias _MutexPointer = UnsafeMutablePointer<pthread_mutex_t> private typealias _RecursiveMutexPointer = UnsafeMutablePointer<pthread_mutex_t> private typealias _ConditionVariablePointer = UnsafeMutablePointer<pthread_cond_t> #endif open class NSLock: NSObject, NSLocking { internal var mutex = _MutexPointer.allocate(capacity: 1) #if os(macOS) || os(iOS) || os(Windows) private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1) private var timeoutMutex = _MutexPointer.allocate(capacity: 1) #endif public override init() { #if os(Windows) #else pthread_mutex_init(mutex, nil) #if os(macOS) || os(iOS) pthread_cond_init(timeoutCond, nil) pthread_mutex_init(timeoutMutex, nil) #endif #endif } deinit { #if os(Windows) #else pthread_mutex_destroy(mutex) #endif mutex.deinitialize(count: 1) mutex.deallocate() #if os(macOS) || os(iOS) || os(Windows) deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex) #endif } open func lock() { #if os(Windows) #else pthread_mutex_lock(mutex) #endif } open func unlock() { #if os(Windows) #else pthread_mutex_unlock(mutex) #if os(macOS) || os(iOS) // Wakeup any threads waiting in lock(before:) pthread_mutex_lock(timeoutMutex) pthread_cond_broadcast(timeoutCond) pthread_mutex_unlock(timeoutMutex) #endif #endif } ... }
我們可以起內(nèi)部是對(duì)pthread_mutex_t的封裝
- 構(gòu)造方法 init()就是調(diào)用了pthread的pthread_mutex_init(mutex, nil)方法
- 析構(gòu)方法 deinit就是調(diào)用了pthread的pthread_mutex_destroy(mutex)方法
- 加鎖方法 lock()就是調(diào)用了pthread的pthread_mutex_lock(mutex)方法
- 解鎖方法 unlock()就是調(diào)用了pthread的pthread_mutex_unlock(mutex)方法
在pthread_mutex中可以通過(guò)pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))來(lái)設(shè)置鎖為遞歸鎖,這里并沒(méi)有設(shè)置,所以NSLock不是一把遞歸鎖!
NSCondition - 互斥鎖
我們通過(guò)查看swift foundation 源碼 可以看到其和NSLock類(lèi)似,也是對(duì)pthread_mutex_t的封裝,相比于NSLock,NSCondition多了幾個(gè)API:
open func wait() { pthread_cond_wait(cond, mutex) } open func wait(until limit: Date) -> Bool { guard var timeout = timeSpecFrom(date: limit) else { return false } return pthread_cond_timedwait(cond, mutex, &timeout) == 0 } open func signal() { pthread_cond_signal(cond) } open func broadcast() { pthread_cond_broadcast(cond) }
- (void)wait 阻塞當(dāng)前線程,使線程進(jìn)入休眠,等待喚醒信號(hào)。調(diào)用前必須已加鎖。
- (void)waitUntilDate 阻塞當(dāng)前線程,使線程進(jìn)入休眠,等待喚醒信號(hào)或者超時(shí)。調(diào)用前必須已加鎖。
- (void)signal 喚醒一個(gè)正在休眠的線程,如果要喚醒多個(gè),需要調(diào)用多次。如果沒(méi)有線程在等待,則什么也不做。調(diào)用前必須已加鎖。
- (void)broadcast 喚醒所有在等待的線程。如果沒(méi)有線程在等待,則什么也不做。調(diào)用前必須已加鎖。
#import "ViewController.h" @interface ViewController () @property (nonatomic ,assign) int count; @property (nonatomic ,strong) NSCondition *iCondition; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.count = 0; self.iCondition = [[NSCondition alloc] init]; // 初始化鎖 } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self nscondition_test]; } #pragma mark -- NSCondition - (void)nscondition_test { // 生產(chǎn) for (int i = 0; i < 50; i ++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self my_production]; }); } // 消費(fèi) for (int i = 0; i < 100; i ++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self my_consumption]; }); } } - (void)my_production { [self.iCondition lock]; self.count ++; NSLog(@"生產(chǎn)了一個(gè)產(chǎn)品,現(xiàn)有產(chǎn)品 : %d個(gè)",self.count); [self.iCondition signal]; // 喚醒一個(gè)wait正在休眠的線程 [self.iCondition unlock]; } - (void)my_consumption { [self.iCondition lock]; while (self.count == 0) { // 這里使用 if 會(huì)出現(xiàn)現(xiàn)有產(chǎn)品是負(fù)數(shù)的情況 [self.iCondition wait]; // 阻塞當(dāng)前線程,使線程進(jìn)入休眠,等待喚醒信號(hào)signal } self.count --; NSLog(@"消費(fèi)了一個(gè)產(chǎn)品,現(xiàn)有產(chǎn)品: %d個(gè)",self.count); [self.iCondition unlock]; } @end
注意??:pthread_mutex 存在虛假喚醒的情況,一個(gè)signl喚醒多個(gè)wait,不是預(yù)期的signal : wait = 1:1效果。 在編碼過(guò)程中可以通過(guò)while條件判斷,使被喚醒的線程,陷入while循環(huán)中,從而解決此問(wèn)題。
NSConditionLock - 互斥鎖
NSConditionLock是基于NSCondition的封裝。目的是讓NSConditionLock自帶條件探測(cè)
open class NSConditionLock : NSObject, NSLocking { internal var _cond = NSCondition() ...... open func lock(whenCondition condition: Int) { let _ = lock(whenCondition: condition, before: Date.distantFuture) } open func `try`() -> Bool { return lock(before: Date.distantPast) } open func tryLock(whenCondition condition: Int) -> Bool { return lock(whenCondition: condition, before: Date.distantPast) } open func unlock(withCondition condition: Int) { _cond.lock() _thread = nil _value = condition _cond.broadcast() _cond.unlock() } ... }
#import "ViewController.h" @interface ViewController () @property (nonatomic ,strong) NSConditionLock *iConditionLock; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self conditonLock_test]; } #pragma mark -- NSConditionLock - (void)conditonLock_test { self.iConditionLock = [[NSConditionLock alloc] initWithCondition:3]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self.iConditionLock lockWhenCondition:3]; NSLog(@"1"); [self.iConditionLock unlockWithCondition:2]; }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self.iConditionLock lockWhenCondition:2]; NSLog(@"2"); [self.iConditionLock unlockWithCondition:1]; }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self.iConditionLock lockWhenCondition:1]; NSLog(@"3"); [self.iConditionLock unlockWithCondition:0]; }); } @end // 線程任務(wù)的執(zhí)行順序:1 2 3
NSConditionLock能夠達(dá)到控制線程執(zhí)行任務(wù)順序的目的。
NSRecursiveLock
遞歸鎖:同一時(shí)刻只能被一條線程所擁有。 NSRecursiveLock是基于pthread的封裝,并設(shè)置了遞歸屬性。
open class NSRecursiveLock: NSObject, NSLocking { internal var mutex = _RecursiveMutexPointer.allocate(capacity: 1) #if os(macOS) || os(iOS) || os(Windows) private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1) private var timeoutMutex = _MutexPointer.allocate(capacity: 1) #endif public override init() { super.init() #if CYGWIN var attrib : pthread_mutexattr_t? = nil #else var attrib = pthread_mutexattr_t() #endif withUnsafeMutablePointer(to: &attrib) { attrs in pthread_mutexattr_init(attrs) // 設(shè)置遞歸屬性 pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE)) pthread_mutex_init(mutex, attrs) } pthread_cond_init(timeoutCond, nil) }
NSConditionLock是一把遞歸鎖,可遞歸加鎖解鎖(可適用于遞歸函數(shù))
通過(guò)PTHREAD_MUTEX_RECURSIVE來(lái)設(shè)置鎖為遞歸鎖。當(dāng)鎖為遞歸鎖的時(shí)候,它的使用場(chǎng)景為單個(gè)線程中的遞歸調(diào)用。
#import "ViewController.h" @interface ViewController () @property (nonatomic ,assign) int count; @property (nonatomic ,strong) NSRecursiveLock *iRecursiveLock; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.count = 0; self.iRecursiveLock = [[NSRecursiveLock alloc] init]; // 初始化鎖 [self recursiveTest]; // 遞歸鎖案例 } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { for (int i = 0; i<10; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self recursiveLock_test]; }); } } #pragma mark -- NSRecursiveLock -(void)recursiveLock_test { [self.iRecursiveLock lock]; self.count ++; NSLog(@"%d",self.count); [self.iRecursiveLock unlock]; } - (void)recursiveTest { dispatch_async(dispatch_get_global_queue(0, 0), ^{ static void (^recursiveMethod)(int); recursiveMethod = ^(int value){ if (value > 0) { [self.iRecursiveLock lock]; NSLog(@"%d",value); recursiveMethod(value - 1); [self.iRecursiveLock unlock]; } }; recursiveMethod(10); }); } @end
如果在不同線程進(jìn)行遞歸調(diào)用的話,會(huì)出現(xiàn)問(wèn)題,把recursiveTest方法放到for循環(huán)里
- (void)recursiveTest { for (int i = 0; i < 5; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ static void (^recursiveMethod)(int); recursiveMethod = ^(int value){ if (value > 0) { [self.iRecursiveLock lock]; NSLog(@"%d",value); recursiveMethod(value - 1); [self.iRecursiveLock unlock]; } }; recursiveMethod(10); }); } }
此時(shí)代碼會(huì)因?yàn)樽泳€程相互等待資源而造成線程死鎖。
@synchronized
@synchronized不管你幾條線程,不管你是否遞歸調(diào)用,它都支持,是我們最常用的一把鎖,雖然都在詬病其性能問(wèn)題,可是在真機(jī)條件下測(cè)試其性能,和其他鎖并沒(méi)有那么明顯的差別。
#import "ViewController.h" @interface ViewController () @property (nonatomic ,assign) int count; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.count = 0; [self synchronized_test]; // synchronized案例 } - (void)synchronized_test { for (int i=0; i<5; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ static void (^recursiveMethod)(int); recursiveMethod = ^(int value){ if (value > 0) { @synchronized(self) { NSLog(@"%d",value); recursiveMethod(value - 1); } } }; recursiveMethod(10); }); } } @end
@synchronized(obj)指令使用的obj為該鎖的唯一標(biāo)識(shí),只有當(dāng)標(biāo)識(shí)相同時(shí),才為滿足互斥。, @synchronized還是個(gè)遞歸可重入鎖,如下代碼所示:
NSObject *obj = [[NSObject alloc] init]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{ @synchronized(obj){ NSLog(@"1開(kāi)始"); @synchronized (obj) { NSLog(@"2開(kāi)始"); @synchronized (obj) { NSLog(@"3"); } NSLog(@"2完成"); } NSLog(@"1結(jié)束"); } });
@synchronized是個(gè)遞歸互斥鎖,同一個(gè)線程可以重復(fù)獲得這個(gè)鎖并進(jìn)入執(zhí)行執(zhí)行塊里面的代碼而不會(huì)導(dǎo)致死鎖。
@synchronized的優(yōu)點(diǎn):不需要在代碼中顯式的創(chuàng)建鎖對(duì)象,便可以實(shí)現(xiàn)鎖的機(jī)制;遞歸互斥,同一個(gè)線程可以重復(fù)進(jìn)入而不導(dǎo)致死鎖。
@synchronized的缺點(diǎn):效率低(在真機(jī)上不見(jiàn)得效率那么低)。@synchronized塊會(huì)隱式的添加一個(gè)異常處理例程來(lái)保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖,這會(huì)增加額外的開(kāi)銷(xiāo)。同時(shí)為了實(shí)現(xiàn)遞歸互斥可重入,底層使用的是遞歸鎖加上復(fù)雜的業(yè)務(wù)邏輯,也增加了不少的消耗。
@synchronized加鎖需要一個(gè)對(duì)象參數(shù),在選著對(duì)象參數(shù)的時(shí)候要特別注意不能讓對(duì)象參數(shù)為nil,否則加鎖無(wú)效。
Semaphore信號(hào)量
同樣的信號(hào)量也可以解決線程安全問(wèn)題,相關(guān)內(nèi)容請(qǐng)查閱GCD篇章,主要是控制并發(fā)數(shù)量,來(lái)實(shí)現(xiàn)線程安全
#pragma mark -- dispatch_semaphore_t - (void)dispatch_semaphore_t_test { dispatch_semaphore_t sem = dispatch_semaphore_create(0); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"任務(wù)1"); dispatch_semaphore_signal(sem); }); dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"任務(wù)2"); dispatch_semaphore_signal(sem); }); dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"任務(wù)3"); }); }
pthread_mutex
純C的鎖,需要我們自己進(jìn)行對(duì)象的內(nèi)存管理,前面有些鎖就是對(duì)齊進(jìn)行的封裝.
#pragma mark -- pthread_mutex - (void)pthread_mutex_test { //非遞歸加鎖 pthread_mutex_t lock0; pthread_mutex_init(&lock0, NULL); pthread_mutex_lock(&lock0); // 鎖住的資源... pthread_mutex_unlock(&lock0); pthread_mutex_destroy(&lock0); // c對(duì)象,需要自己釋放資源 //遞歸加鎖 pthread_mutex_t lock; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 設(shè)置遞歸屬性 pthread_mutex_init(&lock, &attr); pthread_mutexattr_destroy(&attr); pthread_mutex_lock(&lock); // 鎖住的資源... pthread_mutex_unlock(&lock); pthread_mutex_destroy(&lock); // c對(duì)象,需要自己釋放資源 }
讀寫(xiě)鎖
讀寫(xiě)鎖實(shí)際是一種特殊的自旋鎖,它把對(duì)共享資源的訪問(wèn)者劃分成讀者和寫(xiě)者,讀者只對(duì)共享資源進(jìn)行讀訪問(wèn),寫(xiě)者則需要對(duì)共享資源進(jìn)行寫(xiě)操作。這種鎖相對(duì)于自旋鎖而言,能提高并發(fā)性,因?yàn)樵?a rel="external nofollow" target="_blank">多處理器系統(tǒng)中,它允許同時(shí)有多個(gè)讀者來(lái)訪問(wèn)共享資源,最大可能的讀者數(shù)為實(shí)際的邏輯CPU數(shù)。寫(xiě)者是排他性的,一個(gè)讀寫(xiě)鎖同時(shí)只能有一個(gè)寫(xiě)者或多個(gè)讀者(與CPU數(shù)相關(guān)),但不能同時(shí)既有讀者又有寫(xiě)者。在讀寫(xiě)鎖保持期間也是搶占失效的。
如果讀寫(xiě)鎖當(dāng)前沒(méi)有讀者,也沒(méi)有寫(xiě)者,那么寫(xiě)者可以立刻獲得讀寫(xiě)鎖,否則它必須自旋在那里,直到?jīng)]有任何寫(xiě)者或讀者。如果讀寫(xiě)鎖沒(méi)有寫(xiě)者,那么讀者可以立即獲得該讀寫(xiě)鎖,否則讀者必須自旋在那里,直到寫(xiě)者釋放該讀寫(xiě)鎖。
讀寫(xiě)鎖可以實(shí)現(xiàn)多讀單寫(xiě)功能(讀讀并發(fā)、讀寫(xiě)互斥、寫(xiě)寫(xiě)互斥) 我們通過(guò)GCD的柵欄函數(shù)實(shí)現(xiàn)的一個(gè)簡(jiǎn)單讀寫(xiě)鎖案例:
#import "ViewController.h" @interface ViewController () @property (nonatomic ,strong) dispatch_queue_t iQueue; @property (nonatomic ,strong) NSMutableDictionary *dataDic; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.iQueue = dispatch_queue_create("AnAn", DISPATCH_QUEUE_CONCURRENT); self.dataDic = [NSMutableDictionary new]; [self my_write: @"我是寫(xiě)的東西"]; } - (void)test { for (int i = 0; i < 10; i ++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self my_read]; }); } } #pragma mark -- 讀寫(xiě)鎖 - (NSString *)my_read { // 異步讀取 __block NSString *ret; dispatch_sync(self.iQueue, ^{ // 讀取的代碼 ret = self.dataDic[@"name"]; }); NSLog(@"%@",ret); return ret; } - (void)my_write: (NSString *)name { // 寫(xiě)操作 dispatch_barrier_async(self.iQueue, ^{ [self.dataDic setObject:name forKey:@"name"]; }); }
以上就是iOS開(kāi)發(fā)常用線程安全鎖的詳細(xì)內(nèi)容,更多關(guān)于iOS線程安全鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS實(shí)現(xiàn)循環(huán)滾動(dòng)公告欄
這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)循環(huán)滾動(dòng)公告欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03iOS基于UIScrollView實(shí)現(xiàn)滑動(dòng)引導(dǎo)頁(yè)
這篇文章主要為大家詳細(xì)介紹了iOS基于UIScrollView實(shí)現(xiàn)滑動(dòng)引導(dǎo)頁(yè)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01iOS中sqlite數(shù)據(jù)庫(kù)的原生用法
這篇文章主要為大家詳細(xì)介紹了iOS中sqlite數(shù)據(jù)庫(kù)的原生用法,sqlite數(shù)據(jù)庫(kù)相信各位早已耳聞,非常輕巧的一個(gè)數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)僅一個(gè)文件,即建即用,感興趣的小伙伴們可以參考一下32016-05-05iOS開(kāi)發(fā)tips-UINavigationBar的切換效果
這篇文章主要為大家詳細(xì)介紹了iOS開(kāi)發(fā)tips-UINavigationBar的切換效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11iOS開(kāi)發(fā)TableView網(wǎng)絡(luò)請(qǐng)求及展示預(yù)加載實(shí)現(xiàn)示例
這篇文章主要為大家介紹了iOS開(kāi)發(fā)TableView網(wǎng)絡(luò)請(qǐng)求及展示預(yù)加載實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07