線程鎖
是為了解決多個線程之間共享同一資源時,對資源的占用控制,防止多個線程之間同時修改同一資源信息,導致不可預知的問題。
鎖的實現方式大致可以分為以下兩種:
- 阻塞
- 忙等
阻塞:如果鎖對象被其他線程所持有,那么請求訪問的線程就會被加入到等待隊列中,因而被阻塞。這就意味着被阻塞的線程放棄了時間片,調度器會將CPU讓給下一個執行的的線程。當鎖可用的時候,調度器會得到通知,然后根據情況將線程從等待隊列取出來,並重新調度。
OSSpinLock(自旋鎖)
#import <libkern/OSAtomic.h> @interface DrLock : NSObject { OSSpinLock _lock; } - (void)lock; - (int)tryLock; - (void)unlock; @end @implementation DrLock - (instancetype)init{ self = [super init]; if (self) { _lock = OS_SPINLOCK_INIT; } return self; } /// 上鎖 - (void)lock { OSSpinLockLock(&_lock); } /// 上鎖,失敗返回:NO,成功返回:YES - (int)tryLock { return OSSpinLockTry(&_lock); } /// 解鎖 - (void)unlock { OSSpinLockUnlock(&_lock); } @end
關於tryLock的使用,當返回NO時,表示當前鎖已經被鎖住(即:有一個線程在訪問邊界值時被當前鎖鎖住了),此時我們需要調用lock將新的線程鎖住。因此我們可以通過這個方法來判斷當前是否存在多個線程同時訪問邊界值。
os_unfair_lock(互斥鎖)
#import <os/lock.h> @interface DrLock : NSObject { os_unfair_lock _lock; } - (void)lock; - (int)tryLock; - (void)unlock; @end @implementation DrLock - (instancetype)init{ self = [super init]; if (self) { _lock = OS_UNFAIR_LOCK_INIT; } return self; } /// 上鎖 - (void)lock { os_unfair_lock_lock(&_lock); } /// 上鎖,失敗返回:NO,成功返回:YES - (int)tryLock { return os_unfair_lock_trylock(&_lock); } /// 解鎖 - (void)unlock { os_unfair_lock_unlock(&_lock); } @end
關於tryLock的使用,當返回NO時,表示當前鎖已經被鎖住(即:有一個線程在訪問邊界值時被當前鎖鎖住了),此時我們需要調用lock將新的線程鎖住。因此我們可以通過這個方法來判斷當前是否存在多個線程同時訪問邊界值。
dispatch_semaphore_t(信號量)
@interface DrLock : NSObject { dispatch_semaphore_t _lock; } - (void)lock; - (void)unlock; @end @implementation DrLock - (instancetype)init{ self = [super init]; if (self) { _lock = dispatch_semaphore_create(1); } return self; } /// 上鎖 - (void)lock { dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); } /// 解鎖 - (void)unlock { dispatch_semaphore_signal(_lock); } @end
pthread_mutex(互斥鎖)
PTHREAD_MUTEX_NORMAL
、
PTHREAD_MUTEX_ERRORCHECK
、
PTHREAD_MUTEX_RECURSIVE,一般就使用PTHREAD_MUTEX_NORMAL就可以了。
#import <pthread.h> @interface DrLock : NSObject { pthread_mutex_t _lock; } - (void)lock; - (int)tryLock; - (void)unlock; @end @implementation DrLock - (void)dealloc{ pthread_mutex_destroy(&_lock); // 注意這里要銷毀鎖 } - (instancetype)init{ self = [super init]; if (self) { // pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;// 靜態初始化方法 // _lock = lock; // 動態初始化 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // 設置鎖的類型 pthread_mutex_init(&_lock, &attr); pthread_mutexattr_destroy(&attr); } return self; } /// 上鎖 - (void)lock { pthread_mutex_lock(&_lock); } /// 上鎖,返回值==0:成功;返回值!=0:失敗(當互斥量已經被鎖住時調用該函數將返回錯誤代碼EBUSY:16,此時需要調用lock對當前線程上鎖) - (int)tryLock { return pthread_mutex_trylock(&_lock); } /// 解鎖 - (void)unlock { pthread_mutex_unlock(&_lock); } @end
關於tryLock的使用,當返回非0的值時,表示當前鎖已經被鎖住(即:有一個線程在訪問邊界值時被當前鎖鎖住了),此時我們需要調用lock將新的線程鎖住。因此我們可以通過這個方法來判斷當前是否存在多個線程同時訪問邊界值。
- (void)task:(int)ID { int res = [_lock tryLock]; if (res != 0) { NSLog(@"加鎖失敗: %@", @(res)); [_lock lock]; } NSLog(@"執行任務:%@", @(ID)); sleep(2); NSLog(@"執行任務:%@完成", @(ID)); [_lock unlock]; }
pthread_mutex(遞歸鎖)
它與互斥鎖類似,都是采用阻塞當前線程的方式實現加鎖功能,區別在於它可以保證同一個線程可以多次進入同一塊加鎖區域一般情況下。一個線程只能申請一次鎖,也只能在獲得鎖的情況下才能釋放鎖,多次申請鎖或釋放未獲得的鎖都會導致崩潰。假設在已經獲得鎖的情況下再次申請鎖,線程會因為等待鎖的釋放而進入睡眠狀態,因此就不可能再釋放鎖,從而導致死鎖。
然而這種情況經常會發生,比如某個函數申請了鎖,在臨界區內又遞歸調用了自己(出現於遞歸調用的情況)。因此我們需要一個遞歸鎖解決這種情況的調用。
#import <pthread.h> @interface DrLock : NSObject { pthread_mutex_t _lock; } - (void)lock; - (int)tryLock; - (void)unlock; @end @implementation DrLock - (void)dealloc{ pthread_mutex_destroy(&_lock); // 注意這里要銷毀鎖 } - (instancetype)init{ self = [super init]; if (self) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 這里設置的類型為遞歸鎖 pthread_mutex_init(&_lock, &attr); pthread_mutexattr_destroy(&attr); } return self; } /// 上鎖 - (void)lock { pthread_mutex_lock(&_lock); } /// 上鎖,返回值==0:成功;返回值!=0:失敗(當互斥量已經被鎖住時調用該函數將返回錯誤代碼EBUSY:16,此時需要調用lock對當前線程上鎖) - (int)tryLock { return pthread_mutex_trylock(&_lock); } /// 解鎖 - (void)unlock { pthread_mutex_unlock(&_lock); } @end
遞歸鎖與互斥鎖的區別,僅僅在於設置的類型不一樣,遞歸鎖的類型為:PTHREAD_MUTEX_RECURSIVE。下面我們舉個例子,介紹一下這個遞歸鎖的用法。
- (void)task:(int)ID count:(int)count { int i = [_lock tryLock]; if (i != 0) { NSLog(@"加鎖失敗: %@", @(i)); [_lock lock]; } if (count == 0) { NSLog(@"執行任務:%@完成", @(ID)); sleep(1); [_lock unlock]; return; } NSLog(@"執行任務:%@,倒計時:%@", @(ID), @(count)); [self task:ID count:count-1]; [_lock unlock]; }
需要注意的一點,lock與unlock一定要成對執行,即遞歸前后,需要保持lock與unlock的調用次數一致,否則會出現未解鎖的情況,導致線程一直處於阻塞狀態,后續的任務無法進行。
當然遞歸鎖也可以用於非遞歸的調用方法中,只要確保lock與unlock調用次數一致即可。而在遞歸調用的方法中我們必須要使用遞歸鎖了。
NSLock(互斥鎖)
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (void)task:(int)ID { if (![_lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:2]]) { NSLog(@"加鎖失敗"); [_lock lock]; } NSLog(@"執行任務:%@", @(ID)); sleep(2); NSLog(@"執行任務:%@完成", @(ID)); [_lock unlock]; }
以上任務會執行2秒鍾,我們在加鎖的地方采用lockBeforeDate方法,設置一個鎖的超時時間2秒,當2秒后未收到unlock,該方法將不再阻塞線程,返回NO,因此我們增加了一個lock方法,防止多個線程同時調用該方法,導致lockBeforeDate超過2秒。
以上代碼一旦同時存在兩個或以上線程同時調用,就會出現lockBeforeDate超時,導致加鎖失敗。
NSRecursiveLock(遞歸鎖)
- (BOOL)lockBeforeDate:(NSDate *)limit;
NSCondition(條件鎖)
- (void)wait; // 阻塞線程 - (BOOL)waitUntilDate:(NSDate *)limit; // 阻塞線程,並設置一個超時時間 - (void)signal; // 喚醒一個阻塞中的線程 - (void)broadcast; // 喚醒全部阻塞中的線程
該鎖適用於生產者與消費者模式,例如:一個公司負責生產商品,一些消費者負責購買這些商品
/// 生產商品 - (void)production:(NSString *)goodsName { [_condition lock]; [_goodsList addObject:goodsName]; [_condition unlock]; // [_condition signal]; // 通知一個正在排隊的消費者,有貨了,可以購買了 [_condition broadcast]; // 通知所有正在排隊的消費者,有貨了,可以購買了 } /// 購買商品 - (void)buy { [_condition lock]; while (_goodsList.count == 0) { NSLog(@"%@ 正在排隊......", [NSThread currentThread].name); // 沒商品了,排隊等待 // [_condition wait];// 這里會讓當前線程阻塞,並休眠,直到發出signal或broadcast被喚醒,繼續向下執行 // if (![_condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:6]]) { // 最多等6秒,我就不等了 // break; // 等待超時waitUntilDate會返回NO,而通過signal或broadcast喚醒,它會返回YES。因此如果是超時了,那就break跳出循環,向下執行 // } } if (_goodsList.count == 0) { NSLog(@"%@ 沒有買到商品,回家了......", [NSThread currentThread].name); }else { NSString *goods = [_goodsList lastObject]; [_goodsList removeLastObject]; NSLog(@"%@ 買到了 %@", [NSThread currentThread].name, goods); } [_condition unlock]; } // 消費者購買商品 NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(buy) object:nil]; thread1.name = @"jack"; [thread1 start]; NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(buy) object:nil]; thread2.name = @"bob"; [thread2 start]; NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(buy) object:nil]; thread3.name = @"drbox"; [thread3 start]; // 工人生產商品 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_queue_t queue = dispatch_queue_create("workers1", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ [self production:@"蘋果iPhoneX 編號:1001"]; }); }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_queue_t queue = dispatch_queue_create("workers2", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ [self production:@"蘋果iPhoneX 編號:1002"]; }); }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_queue_t queue = dispatch_queue_create("workers3", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ [self production:@"蘋果iPhoneX 編號:1003"]; }); });
以上為典型的生產者與消費者模式,當生產者生產出一件商品,通知一個或全部排隊中的消費者購買。注意,這里有兩點注意事項:
- 由於生產商品為多線程,因此需要對goodsList操作前加鎖。
- 通知消費者時調用signal與broadcast的不同,signal只會喚起一個休眠中的線程,而broadcast則會喚起全部休眠中的線程,因此我們在buy方法中增加了一個while循環來判斷,防止broadcast喚起全部休眠的線程,導致有些線程錯誤的處理產品是否為空的情況
pthread_mutex_t + pthread_cond_t(實現條件鎖)
#import <pthread.h> @interface DrLock : NSObject { pthread_mutex_t _lock; pthread_cond_t _cond; // 條件 } - (void)lock; - (void)unlock; @end @implementation DrLock - (void)dealloc{ pthread_mutex_destroy(&_lock); // 注意這里要銷毀鎖 pthread_cond_destroy(&_cond); } - (instancetype)init{ self = [super init]; if (self) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); // 這里設置的類型為互斥鎖 pthread_mutex_init(&_lock, &attr); pthread_mutexattr_destroy(&attr); pthread_condattr_t conAttr; pthread_condattr_init(&conAttr); pthread_condattr_setpshared(&conAttr, PTHREAD_PROCESS_PRIVATE); pthread_cond_init(&_cond, &conAttr); } return self; } /// 上鎖 - (void)lock { pthread_mutex_lock(&_lock); } /// 解鎖 - (void)unlock { pthread_mutex_unlock(&_lock); } /// 阻塞當前線程 - (void)wait { pthread_cond_wait(&_cond, &_lock); } /// 阻塞當前線程 - (BOOL)waitUntilDate:(NSDate *)date { struct timespec time; time.tv_sec = date.timeIntervalSince1970; time.tv_nsec = 0; // 這里必須設置一個數,否則計時不起作用,具體設置多少我不知道這里如何設置,有清楚的可以回復我,謝謝! return pthread_cond_timedwait(&_cond, &_lock, &time) == 0; } /// 喚醒一個被阻塞的線程 - (void)signal { pthread_cond_signal(&_cond); } /// 喚醒全部阻塞中的線程 - (void)broadcast { pthread_cond_broadcast(&_cond); } @end
這里我們用到了一個pthread_cond_t條件(記得需要手動釋放資源),它在初始化時,需要設置一個屬性值,這里我們設置成PTHREAD_PROCESS_PRIVATE,實際上這個也是缺省值。
關於pthread_condattr_setpshared的值范圍有以下說明:
因為pthread_mutex屬於 POSIX 線程下的互斥鎖,因此它的取值包括:PTHREAD_PROCESS_SHARED和PTHREAD_PROCESS_PRIVATE
NSConditionLock(升級版的條件鎖)
/// 生產商品 - (void)production:(NSString *)goodsName { [_conditionLock lockWhenCondition:1]; // 只有當前鎖的條件為1時,當前線程才可以獲得該鎖來訪問資源,否則會被阻塞。 [_goodsList addObject:goodsName]; [_conditionLock unlockWithCondition:2]; // 解除鎖,並設置鎖的條件為2 } /// 購買商品 - (void)buy { NSLog(@"%@ 排隊中......", [NSThread currentThread].name); // [_conditionLock lockWhenCondition:2]; // 只有當前鎖的條件為2時,當前線程才可以獲得該鎖來訪問資源,否則會被阻塞。 // 我們還可以通過下面方法,來指定一個等待時間,當時間到了,如果當前線程依然沒有獲得鎖,該線程會自動被喚起 [_conditionLock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:6]]; if (_goodsList.count == 0) { NSLog(@"%@ 沒有買到商品,回家了......", [NSThread currentThread].name); }else { NSString *goods = [_goodsList lastObject]; [_goodsList removeLastObject]; NSLog(@"%@ 買到了 %@", [NSThread currentThread].name, goods); } [_conditionLock unlockWithCondition:1]; // 解鎖,並設置鎖的條件為1,目的是將獲取鎖的權限交給了生產商品的線程 }
我們在初始化條件鎖的時候,設置了一個條件值為1
if (!_conditionLock) { _conditionLock = [[NSConditionLock alloc] initWithCondition:1]; }
從這個例子可知,我們可以通過設置一個條件值,來控制哪個線程可以獲得鎖,從而獲取資源。這樣一來,我們就可以通過這個條件值來保持多個線程的執行順序了。
下面是該條件鎖的api方法,如下:
- (void)lockWhenCondition:(NSInteger)condition; - (BOOL)tryLock; - (BOOL)tryLockWhenCondition:(NSInteger)condition; - (void)unlockWithCondition:(NSInteger)condition; - (BOOL)lockBeforeDate:(NSDate *)limit; - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit; - (void)lock; - (void)unlock;
我們需要注意的是,lock與unlock只是對當前線程進行的加鎖與解鎖,並不受條件值的影響。因此我們在使用lockWhenCondition加鎖的時候,如果調用unlock解鎖,那么只會解鎖當前線程,並不會改變條件值,此時你需要再次調用unlockWithCondition來改變條件值,在實際使用中我想lockWhenCondition與unlockWithCondition應該是成對使用的,除非你有特殊的需求。
@synchronized(遞歸鎖)
這個鎖我們在開發中也經常看到,也實際使用過,實際上它只是一種語法糖,底層依然是我們上面用到的鎖。
我們知道 @synchronized 后面需要緊跟一個 OC 對象,它實際上是把這個對象當做鎖來使用。這是通過一個哈希表來實現的,OC 在底層使用了一個遞歸鎖的數組(你可以理解為鎖池),通過對對象取哈希值來得到對應的遞歸鎖。(注意:有些文章中提到的這里是一個互斥鎖,實際上是錯誤的,它是采用pthread_mutex實現的遞歸鎖,為啥是遞歸鎖,我們在實際使用中也可以發現,@synchronized是可以嵌套的,嵌套中的OC對象可以是相同的,既然是相同的,那么其對應的鎖肯定也是一樣的,我們知道,同一個鎖不能被同一個線程捕獲,而只有遞歸鎖允許)
其內部實現實則調用如下兩個函數:
// 加鎖 func objc_sync_enter(_ obj: Any) -> Int32 // 解鎖 func objc_sync_exit(_ obj: Any) -> Int32
Atomic(原子操作)
狹義上的原子操作表示一條不可打斷的操作,也就是說線程在執行操作過程中,不會被操作系統掛起,而是一定會執行完。在單處理器環境下,一條匯編指令顯然是原子操作,因為中斷也要通過指令來實現。
然而在多處理器的情況下,能夠被多個處理器同時執行的操作任然算不上原子操作。因此,真正的原子操作必須由硬件提供支持,比如 x86 平台上如果在指令前面加上 “LOCK” 前綴,對應的機器碼在執行時會把總線鎖住,使得其他 CPU不能再執行相同操作,從而從硬件層面確保了操作的原子性。
我們在iOS開發中經常使用的@property (atomic, assign)聲明屬性,其中atomic就是指定當前屬性為原子操作。
還有#import <libkern/OSAtomic.h>這個庫中的函數,也是原子操作的,如下:
int32_t res = OSAtomicIncrement32(&_count); // 對count進行原子+1的操作,返回操作后的結果
不過這個在iOS10之后被廢棄了,改用#import<stdatomic.h>下的如下調用(具體可以看這里):
@property (nonatomic, assign) _Atomic(int) count; int32_t res = atomic_fetch_add(&_count, 2); // 對count進行原子+2的操作,返回操作前的結果
pthread_rwlock_t(讀寫鎖)
- 讀模式下加鎖狀態(讀鎖)
- 寫模式下加鎖狀態(寫鎖)
- 不加鎖狀態
#import <pthread.h> @interface DrLock : NSObject { pthread_rwlock_t _lock; } - (void)rdLock; - (int)tryRdLock; - (void)wrLock; - (int)tryWrLock; - (void)unlock; @end @implementation DrLock - (void)dealloc{ pthread_rwlock_destroy(&_lock); } - (instancetype)init{ self = [super init]; if (self) { // pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER; // 靜態初始化方法 // _lock = lock; pthread_rwlockattr_t attr; pthread_rwlockattr_init(&attr); pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); // 這里設置為僅適用於當前進程所創建的線程 pthread_rwlock_init(&_lock, &attr); pthread_rwlockattr_destroy(&attr); } return self; } /// 讀模式加鎖,當前線程無法獲取鎖時,將會阻塞當前線程 - (void)rdLock{ pthread_rwlock_rdlock(&_lock); }
/// 讀模式鎖,當前線程無法獲得鎖時,不會阻塞當前線程,直接返回EBUSY - (int)tryRdLock{ return pthread_rwlock_tryrdlock(&_lock); } /// 寫模式加鎖,當前線程無法獲取鎖時,將會阻塞當前線程 - (void)wrLock{ pthread_rwlock_wrlock(&_lock); }
/// 寫模式鎖,當前線程無法獲得鎖時,不會阻塞當前線程,直接返回EBUSY - (int)tryWrLock{ return pthread_rwlock_trywrlock(&_lock); } /// 解鎖 - (void)unlock{ pthread_rwlock_unlock(&_lock); } @end
當一個線程擁有了讀鎖時,執行讀操作,此時仍然可以有多個線程擁有讀鎖。但是此時一個線程嘗試擁有寫鎖時,這個線程將被阻塞,直到讀鎖被解鎖為止。
當一個線程擁有寫鎖時,執行寫操作,此時其他線程將不能獲得寫鎖,並且其他線程也不能擁有讀鎖。
這就是寫獨占、讀共享。
從上面的api中我們可以看到讀鎖和寫鎖擁有兩種方法,一個是帶try的,一個是不帶try的。帶try的方法在不能獲得鎖的情況下,不會阻塞當前線程,而是立刻返回一個EBUSY錯誤。而不帶try的方法,在當前線程無法獲得鎖時,將會阻塞當前線程,直到其他線程解了鎖。
dispatch_barrier_async(GCD柵欄實現讀寫鎖)
// 並行隊列 dispatch_queue_t queue = dispatch_queue_create("CONCURRENT", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"執行任務1"); sleep(1); }); dispatch_async(queue, ^{ NSLog(@"執行任務2"); sleep(1); }); dispatch_barrier_async(queue, ^{ // 一個異步柵欄,它不會阻塞當前線程 NSLog(@"執行柵欄操作1"); sleep(1); }); dispatch_barrier_sync(queue, ^{ // 一個同步柵欄,它會阻塞當前線程 NSLog(@"執行柵欄操作2"); sleep(1); }); dispatch_async(queue, ^{ NSLog(@"執行任務3"); sleep(1); }); dispatch_async(queue, ^{ NSLog(@"執行任務4"); sleep(1); }); NSLog(@"代碼執行完畢");
輸入結果如下:
執行任務1 執行任務2 執行柵欄操作1 執行柵欄操作2 代碼執行完畢 // 這里被同步柵欄阻塞了,因此在同步柵欄之后執行的 執行任務3 執行任務4
經過上面的事例,我們已經對柵欄有了了解,接下來我們舉個實際應用:
@interface MessageManager : NSObject{ NSMutableArray *_msgList; dispatch_queue_t _queue; } - (NSString *)getMessage; - (void)setMessage:(NSString *)msg; @end @implementation MessageManager - (instancetype)init { self = [super init]; if (self) { _msgList = [@[] mutableCopy]; _queue = dispatch_queue_create("lock", DISPATCH_QUEUE_CONCURRENT); // 為了提高讀的效率,這里采用並行隊列 } return self; } - (NSString *)getMessage{ __block NSString *msg = nil; dispatch_sync(_queue, ^{ // 采用同步讀操作 msg = [self -> _msgList componentsJoinedByString:@", "]; }); return msg; } - (void)setMessage:(NSString *)msg{ dispatch_barrier_async(_queue, ^{ // 采用異步柵欄,執行寫操作 [self -> _msgList addObject:msg]; }); } @end
上面是一個利用GCD柵欄實現的一個讀寫消息的操作
- 為了提高讀的效率,我們采用並行隊列實現。
- 讀消息是立即返回的,因此采用隊列的同步操作。
- 寫消息可能會花費一定時間,因此我們采用異步柵欄,這樣不會阻塞當前線程
那么除了GCD的柵欄可以實現讀寫鎖,可不可以采用GCD的group實現呢?答案是不可以。我們要清楚一點,讀寫鎖的特點是什么?寫獨占,讀共享,也就是多讀少寫。明白了這一點,我們就會知道為什么不能用group實現了。group是采用dispatch_group_wait與dispatch_group_leave來實現同步操作的,這也就導致不能實現寫獨占,讀共享了。