
多線程安全問(wèn)題
多個(gè)線程可能訪問(wèn)同一塊資源,比如同一個(gè)文件,同一個(gè)對(duì)象,同一個(gè)變量等;當(dāng)多個(gè)線程訪問(wèn)同一資源時(shí),容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題;
如下面這個(gè)經(jīng)典圖所示,線程A、B均訪問(wèn)了Integer變量,但最終的結(jié)果(18)可能并不是我們想要的(19);

如果要保證共享的數(shù)據(jù)是正確的安全的,就需要使用線程同步技術(shù):讓多個(gè)線程間按順序執(zhí)行而不是并發(fā)執(zhí)行;常見(jiàn)的線程同步技術(shù)就是加鎖,同上面例子一樣,加鎖后能保證最終結(jié)果是正常的;

iOS中的線程同步方案常見(jiàn)的有以下幾種:
- pthread相關(guān)方案
- OSSpinLock
- os_unfair_lock
- GCD相關(guān)方案
- NSOperationQueue相關(guān)方案
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
pthread相關(guān)方案
pthread是跨平臺(tái)的,而且更加底層;我們先來(lái)了解pthread相關(guān)的鎖;
PTHREAD_MUTEX_NORMAL 普通互斥鎖
互斥鎖的機(jī)制:被這個(gè)鎖保護(hù)的臨界區(qū)就只允許一個(gè)線程進(jìn)入,其它線程如果沒(méi)有獲得鎖權(quán)限,那就只能在外面等著;等待鎖的線程會(huì)處于休眠狀態(tài),處于休眠狀態(tài)不會(huì)占用CPU資源;
pthread_mutex的使用:
// 兩種初始化方式
// 1.靜態(tài)初始化
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 2.動(dòng)態(tài)創(chuàng)建
pthread_mutex_t lock1;
pthread_mutex_init(&lock1, NULL); // 可以根據(jù)需要配置pthread_mutexattr NULL默認(rèn)為互斥鎖
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
pthread_mutex_lock(&lock); // 加鎖
NSLog(@"%@===write===start",[NSThread currentThread]);
sleep(3);
NSLog(@"%@===write===end",[NSThread currentThread]);
pthread_mutex_unlock(&lock); // 解鎖
}];
[queue addOperationWithBlock:^{
pthread_mutex_lock(&lock);
NSLog(@"%@===read===start",[NSThread currentThread]);
sleep(2);
NSLog(@"%@===read===end",[NSThread currentThread]);
pthread_mutex_unlock(&lock);
}];
兩個(gè)線程任務(wù)同步執(zhí)行:
<NSThread: 0x2834863c0>{number = 5, name = (null)}===write===start
<NSThread: 0x2834863c0>{number = 5, name = (null)}===write===end
<NSThread: 0x2834848c0>{number = 6, name = (null)}===read===start
<NSThread: 0x2834848c0>{number = 6, name = (null)}===read===end
// 或
<NSThread: 0x283486200>{number = 8, name = (null)}===read===start
<NSThread: 0x283486200>{number = 8, name = (null)}===read===end
<NSThread: 0x283486480>{number = 7, name = (null)}===write===start
<NSThread: 0x283486480>{number = 7, name = (null)}===write===end
PTHREAD_MUTEX_RECURSIVE 遞歸鎖
顧名思義,遞歸鎖用于遞歸調(diào)用加鎖的情況;對(duì)于遞歸調(diào)用的加鎖,如果使用上面normal鎖,則會(huì)出現(xiàn)死鎖;遞歸鎖就是保證了對(duì)同一把鎖能多次加鎖,而不用等待解鎖,從而避免了遞歸造成的死鎖問(wèn)題;
- (void)synchronizedTest {
pthread_mutexattr_t att;
pthread_mutexattr_init(&att);
pthread_mutexattr_settype(&att, PTHREAD_MUTEX_RECURSIVE); // PTHREAD_MUTEX_NORMAL普通互斥鎖 PTHREAD_MUTEX_RECURSIVE遞歸鎖
pthread_mutex_init(&_lock, &att);
pthread_mutexattr_destroy(&att);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[self recursiveTest:3]; // 遞歸調(diào)用
}];
}
// 遞歸方法
- (void)recursiveTest:(NSInteger)value {
pthread_mutex_lock(&_lock);
if (value > 0) {
NSLog(@"%@===start",[NSThread currentThread]);
sleep(1);
NSLog(@"%@===end",[NSThread currentThread]);
[self recursiveTest:value-1];
}
pthread_mutex_unlock(&_lock);
}
輸出正確的結(jié)果:
<NSThread: 0x280d642c0>{number = 3, name = (null)}===start
<NSThread: 0x280d642c0>{number = 3, name = (null)}===end
<NSThread: 0x280d642c0>{number = 3, name = (null)}===start
<NSThread: 0x280d642c0>{number = 3, name = (null)}===end
<NSThread: 0x280d642c0>{number = 3, name = (null)}===start
<NSThread: 0x280d642c0>{number = 3, name = (null)}===end
pthread_rwlock 讀寫(xiě)鎖
以上鎖能很好的解決線程安全問(wèn)題,但是這樣的話同一時(shí)間,只會(huì)有一個(gè)線程能執(zhí)行;有時(shí)我們的需求并不希望這樣,比如讀寫(xiě)操作:我們希望讀是不受同步機(jī)制限制,即允許多個(gè)線程同時(shí)讀;對(duì)于寫(xiě),我們希望同一時(shí)間只允許一個(gè)線程操作;同時(shí),在寫(xiě)操作進(jìn)行時(shí)不允許同時(shí)讀;而讀寫(xiě)鎖就是為這種場(chǎng)景而生的:
pthread_rwlock 讀寫(xiě)鎖與基本的互斥鎖的創(chuàng)建使用方式大同小異:
// 兩種初始化方式
// 1.靜態(tài)初始化
static pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
// 2.動(dòng)態(tài)創(chuàng)建
static pthread_rwlock_t lock1;
pthread_rwlock_init(&lock1, NULL);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 3; i ++) {
[queue addOperationWithBlock:^{
pthread_rwlock_wrlock(&lock);
NSLog(@"%@===write===start",[NSThread currentThread]);
sleep(3);
NSLog(@"%@===write===end",[NSThread currentThread]);
pthread_rwlock_unlock(&lock);
}];
}
for (int i = 0; i < 3; i ++) {
[queue addOperationWithBlock:^{
pthread_rwlock_rdlock(&lock);
NSLog(@"%@===read===start",[NSThread currentThread]);
sleep(2);
NSLog(@"%@===read===end",[NSThread currentThread]);
pthread_rwlock_unlock(&lock);
}];
}
結(jié)果中多個(gè)read是可以并發(fā)的,write是同步執(zhí)行的;
<NSThread: 0x281b83440>{number = 5, name = (null)}===write===start
<NSThread: 0x281b83440>{number = 5, name = (null)}===write===end
<NSThread: 0x281b83400>{number = 6, name = (null)}===write===start
<NSThread: 0x281b83400>{number = 6, name = (null)}===write===end
<NSThread: 0x281b94940>{number = 4, name = (null)}===write===start
<NSThread: 0x281b94940>{number = 4, name = (null)}===write===end
<NSThread: 0x281b9aa00>{number = 3, name = (null)}===read===start
<NSThread: 0x281b864c0>{number = 7, name = (null)}===read===start
<NSThread: 0x281b87780>{number = 8, name = (null)}===read===start
<NSThread: 0x281b87780>{number = 8, name = (null)}===read===end
<NSThread: 0x281b9aa00>{number = 3, name = (null)}===read===end
<NSThread: 0x281b864c0>{number = 7, name = (null)}===read===end
pthread_join
使用場(chǎng)景:有A,B兩個(gè)線程,B線程在做某些事情之前,必須要等待A線程把事情做完,然后才能接著做下去。這時(shí)候就可以用join。
static pthread_t thread1;
static pthread_t thread2;
void * writeFunc(void *args) {
NSLog(@"%u===write===start",(unsigned int)pthread_self());
sleep(3);
NSLog(@"%u===write===end",(unsigned int)pthread_self());
pthread_exit(NULL);
return NULL;
}
void* readFunc(void *args) {
pthread_join(thread1, NULL);
NSLog(@"%u===read===start",(unsigned int)pthread_self());
sleep(2);
NSLog(@"%u===read===end",(unsigned int)pthread_self());
return NULL;
}
- (void)synchronizedTest {
pthread_create(&thread1, NULL, writeFunc, NULL);
pthread_create(&thread2, NULL, readFunc, NULL);
}
這樣就保證了read一定是在write后
871015936===write===start
871015936===write===end
871589376===read===start
871589376===read===end
pthread_cond 條件鎖
條件鎖能在合適的時(shí)候喚醒正在等待的線程。具體什么時(shí)候合適由程序員自己控制條件變量決定;
具體的場(chǎng)景就是:
B線程和A線程之間有合作關(guān)系,當(dāng)A線程完操作前,B線程會(huì)等待。當(dāng)A線程完成后,需要讓B線程知道,然后B線程從等待狀態(tài)中被喚醒,然后處理自己的任務(wù)。
// 1.靜態(tài)初始化
static pthread_cond_t cond_lock = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER; // 需要配合mutex互斥鎖使用
// 2.動(dòng)態(tài)創(chuàng)建
static pthread_cond_t cond_lock1;
pthread_cond_init(&cond_lock1, NULL);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
pthread_mutex_lock(&mutex_lock);
while (self.condition_value <= 0) { // 條件成立則暫時(shí)解鎖并等待
pthread_cond_wait(&cond_lock, &mutex_lock);
}
NSLog(@"%@===read===start",[NSThread currentThread]);
sleep(2);
NSLog(@"%@===read===end",[NSThread currentThread]);
pthread_mutex_unlock(&mutex_lock);
}];
[queue addOperationWithBlock:^{
pthread_mutex_lock(&mutex_lock);
NSLog(@"%@===write===start",[NSThread currentThread]);
sleep(3);
self.condition_value = 1; // 一定要更改條件 否則上面read線程條件成立又會(huì)wait
NSLog(@"%@===write===end",[NSThread currentThread]);
pthread_cond_signal(&cond_lock); // 傳遞信號(hào)給等待的線程 而且是在解鎖前
// pthread_cond_broadcast(pthread_cond_t * _Nonnull) // 通知所有線程
pthread_mutex_unlock(&mutex_lock);
}];
<NSThread: 0x283783e40>{number = 3, name = (null)}===write===start
<NSThread: 0x283783e40>{number = 3, name = (null)}===write===end
<NSThread: 0x28379aa40>{number = 4, name = (null)}===read===start
<NSThread: 0x28379aa40>{number = 4, name = (null)}===read===end
這里有幾個(gè)需要注意的地方:
- 一定要配合互斥鎖使用;
- 一定要判斷條件并更改條件;
- 最好使用while做條件判斷(而不是if)
- 發(fā)送信號(hào)時(shí),最好在臨近區(qū)內(nèi)發(fā)送(即互斥鎖范圍內(nèi));
以上幾點(diǎn)的原因,可以參考下面大神的文章;
semaphore 信號(hào)量
信號(hào)量維護(hù)了一個(gè)unsigned int類型的value,通過(guò)這個(gè)值控制線程同步;具體有以下使用場(chǎng)景:
- 信號(hào)量的初始值設(shè)為1,代表同時(shí)只允許1條線程訪問(wèn)資源,保證線程同步
// 創(chuàng)建 原型sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);
// name 信號(hào)的外部名字
// oflag 選擇創(chuàng)建或打開(kāi)一個(gè)現(xiàn)有的信號(hào)燈
// mode 權(quán)限位
// value 信號(hào)初始值
sem_t * sem = sem_open("semname", O_CREAT, 0644, 1);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
sem_wait(sem); // 首先判斷信號(hào)量value 如果=0則等待,否則value-1并正常往下走
NSLog(@"%@===write===start",[NSThread currentThread]);
sleep(3);
NSLog(@"%@===write===end",[NSThread currentThread]);
sem_post(sem); // 執(zhí)行完發(fā)送信號(hào),value+1
}];
[queue addOperationWithBlock:^{
sem_wait(sem);
NSLog(@"%@===read===start",[NSThread currentThread]);
sleep(2);
NSLog(@"%@===read===end",[NSThread currentThread]);
sem_post(sem);
}];
- 信號(hào)量的初始值value,可以用來(lái)控制線程并發(fā)訪問(wèn)的最大數(shù)量
sem_t *sem = sem_open("semname_count", O_CREAT, 0644, 3);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 21; i ++) {
[queue addOperationWithBlock:^{
sem_wait(sem);
NSLog(@"%@===write===start",[NSThread currentThread]);
sleep(2);
NSLog(@"%@===write===end",[NSThread currentThread]);
sem_post(sem);
}];
}
輸出結(jié)果可以看出最多最會(huì)有3個(gè)線程:
<NSThread: 0x280431380>{number = 6, name = (null)}===write===start
<NSThread: 0x28040cb80>{number = 5, name = (null)}===write===start
<NSThread: 0x280431500>{number = 7, name = (null)}===write===start
<NSThread: 0x28040cb80>{number = 5, name = (null)}===write===end
<NSThread: 0x28040cb80>{number = 5, name = (null)}===write===start
<NSThread: 0x280431380>{number = 6, name = (null)}===write===end
<NSThread: 0x280431380>{number = 6, name = (null)}===write===start
以上代碼,其實(shí)就類似設(shè)置NSOperationQueue的maxConcurrentOperationCount效果;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 3;
OSSpinLock自旋鎖
自旋鎖的作用同互斥鎖一樣,不同于互斥鎖的線程休眠機(jī)制,自旋鎖等待的線程會(huì)忙等,也就是等待的過(guò)程其實(shí)是在跑一個(gè)while循環(huán);這樣等待的過(guò)程同樣消耗CPU資源,但這種方式不會(huì)涉及線程喚醒、休眠的切換,性能會(huì)高點(diǎn);
__block OSSpinLock lock = OS_SPINLOCK_INIT;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
OSSpinLockLock(&lock);
NSLog(@"%@===write===start",[NSThread currentThread]);
sleep(3);
NSLog(@"%@===write===end",[NSThread currentThread]);
OSSpinLockUnlock(&lock);
}];
[queue addOperationWithBlock:^{
OSSpinLockLock(&lock);
NSLog(@"%@===read===start",[NSThread currentThread]);
sleep(2);
NSLog(@"%@===read===end",[NSThread currentThread]);
OSSpinLockUnlock(&lock);
}];
同樣能同步執(zhí)行,但代碼會(huì)有警告:
'OSSpinLockUnlock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock_unlock() from <os/lock.h> instead
這是因?yàn)镺SSpinLock已經(jīng)不再安全了,會(huì)有優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題;
多線程并發(fā)處理,原理上說(shuō)是CPU時(shí)間片輪轉(zhuǎn)機(jī)制,即將時(shí)間劃分為極小單位,每個(gè)線程依次執(zhí)行這極段的時(shí)間;這樣多個(gè)線程看起來(lái)是同時(shí)執(zhí)行的;另外,不同的線程有可能是不同的優(yōu)先級(jí);高優(yōu)先級(jí)的線程要占用較長(zhǎng)的時(shí)間、CPU資源;高優(yōu)先級(jí)線程始終會(huì)在低優(yōu)先級(jí)線程前執(zhí)行,一個(gè)線程不會(huì)受到比它更低優(yōu)先級(jí)線程的干擾。
如果使用自旋鎖,且一個(gè)低優(yōu)先級(jí)的線程先于高優(yōu)先級(jí)的線程獲得鎖并訪問(wèn)共享資源;同時(shí)高優(yōu)先級(jí)的線程也會(huì)嘗試獲取鎖,獲取鎖失敗就一直忙等,忙等狀態(tài)占用大量CPU資源;而低優(yōu)先級(jí)的線程也需要CPU資源,但是競(jìng)爭(zhēng)不過(guò)從而導(dǎo)致任務(wù)遲遲完不成,無(wú)法解鎖;
蘋(píng)果給的建議是使用os_unfair_lock替代,但這個(gè)最低只支持iOS10;
__block os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; // 初始化
os_unfair_lock_lock(&lock); // 加鎖
os_unfair_lock_unlock(&lock); // 解鎖
NSLock
這個(gè)其實(shí)就是對(duì)pthread_mutex普通互斥鎖的封裝;面向?qū)ο?,使用起?lái)更方便;
- (void)lock;
- (void)unlock;
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
NSRecursiveLock 遞歸鎖
對(duì)pthread_mutex遞歸鎖的封裝,方法和上面的一樣;
NSCondition
對(duì)pthread_cond條件鎖的封裝,使用pthread_cond需要配合pthread_mutex互斥鎖使用,NSCondition封裝好了,一把鎖就能實(shí)現(xiàn):
NSCondition *lock = [[NSCondition alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[lock lock];
while (self.condition_value <= 0) { // 條件成立則暫時(shí)解鎖并等待
[lock wait];
}
NSLog(@"%@===read===start",[NSThread currentThread]);
sleep(2);
NSLog(@"%@===read===end",[NSThread currentThread]);
[lock unlock];
}];
[queue addOperationWithBlock:^{
[lock lock];
NSLog(@"%@===write===start",[NSThread currentThread]);
sleep(3);
self.condition_value = 1; // 一定要更改條件 否則上面read線程條件成立又會(huì)wait
NSLog(@"%@===write===end",[NSThread currentThread]);
[lock signal]; // 傳遞信號(hào)給等待的線程 而且是在解鎖前
// [lock broadcast] // 通知所有線程
[lock unlock];
}];
NSConditionLock
對(duì)NSCondition的進(jìn)一步封裝,在NSCondition基礎(chǔ)上,加了可控制的條件condition;通過(guò)條件變量,控制通知哪條線程;
@property (readonly) NSInteger condition;
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1]; // 初始化,設(shè)置condition=1
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[lock lockWhenCondition:1]; // 當(dāng)condition=1時(shí) 獲取鎖成功 否則等待(但是首次使用lockWhenCondition時(shí)condition不對(duì)時(shí)也能獲取鎖成功)
NSLog(@"%@===A===start",[NSThread currentThread]);
sleep(2);
NSLog(@"%@===A===end",[NSThread currentThread]);
// unlock根據(jù)不同的條件 控制對(duì)應(yīng)的線程
[lock unlockWithCondition:2]; // 解鎖,同時(shí)設(shè)置condition=2并signal;
// [lock unlockWithCondition:3];
}];
[queue addOperationWithBlock:^{
[lock lockWhenCondition:2];
NSLog(@"%@===B===start",[NSThread currentThread]);
sleep(1);
NSLog(@"%@===B===end",[NSThread currentThread]);
[lock unlock];
}];
[queue addOperationWithBlock:^{
[lock lockWhenCondition:3];
NSLog(@"%@===C===start",[NSThread currentThread]);
sleep(1);
NSLog(@"%@===C===end",[NSThread currentThread]);
[lock unlock];
}];
線程A解鎖時(shí)可以傳不同條件值,對(duì)應(yīng)條件值的其他等待線程就會(huì)被喚醒;這里條件值為2,則執(zhí)行線程B任務(wù);條件設(shè)置為3,則執(zhí)行線程C任務(wù);如果是其他值則線程B,C繼續(xù)一直等待;
NSThread: 0x282b66340>{number = 6, name = (null)}===A===start
NSThread: 0x282b66340>{number = 6, name = (null)}===A===end
NSThread: 0x282b68240>{number = 3, name = (null)}===B===start
NSThread: 0x282b68240>{number = 3, name = (null)}===B===end
@synchronized
是對(duì)mutex遞歸鎖的封裝;
@synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖,然后進(jìn)行加鎖、解鎖操作;一個(gè)對(duì)象對(duì)應(yīng)一把鎖;
NSObject *obj = [[NSObject alloc] init];
@synchronized (obj) {
// ...
}
GCD相關(guān)
dispatch_semaphore信號(hào)量
這個(gè)和上篇講的semaphore差不多;
// 創(chuàng)建信號(hào)量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
// 判斷信號(hào)量,如果=0則等待,否則信號(hào)值-1往下執(zhí)行
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
// 發(fā)送信號(hào)量,信號(hào)值+1
dispatch_semaphore_signal(sem);
DISPATCH_QUEUE_SERIAL 串行隊(duì)列
串行隊(duì)列的任務(wù)就是同步執(zhí)行的;
dispatch_queue_t queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// ThreadA dosomething....
});
dispatch_async(queue, ^{
// ThreadB dosomething....
});
dispatch_group
將任務(wù)分組,組內(nèi)任務(wù)異步執(zhí)行;當(dāng)所有任務(wù)執(zhí)行完后,可以通知其他線程執(zhí)行任務(wù):
// group必須使用自己創(chuàng)建的并發(fā)隊(duì)列 使用global全局隊(duì)列無(wú)效
dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0); xxx
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
sleep(1);
NSLog(@"%@===TaskA",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(1);
NSLog(@"%@===TaskB",[NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"%@===TaskC",[NSThread currentThread]);
});
// dispatch_async(queue, ^{
// dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC))); // 可以設(shè)置等待的超時(shí)時(shí)間
// NSLog(@"%@===TaskC",[NSThread currentThread]);
// });
以上代碼對(duì)應(yīng)的場(chǎng)景就是:A,B線程可以并發(fā)執(zhí)行,但C線程一定要在AB線程執(zhí)行完后再執(zhí)行;
dispatch_group_notify也可以使用dispatch_group_wait替代,一樣是阻塞的作用,而dispatch_group_wait能設(shè)置等待超時(shí)時(shí)間;超過(guò)時(shí)間將不再阻塞,繼續(xù)任務(wù);
還有一點(diǎn)需要注意的是,dispatch_group必須使用自己創(chuàng)建的并發(fā)隊(duì)列, 使用global全局隊(duì)列無(wú)效,使用串行隊(duì)列沒(méi)有意義;
dispatch_barrier
如同它的名字一樣,dispatch_barrier就是起到一個(gè)柵欄的作用;柵欄兩邊的任務(wù)可以并發(fā)執(zhí)行,柵欄里的任務(wù)必須等到柵欄上邊的任務(wù)執(zhí)行完才執(zhí)行,柵欄下邊的任務(wù)必須等柵欄里的任務(wù)執(zhí)行完后才執(zhí)行;
dispatch_barrier其實(shí)就是阻塞隊(duì)列的作用;
這個(gè)其實(shí)也可以通過(guò)dispatch_group實(shí)現(xiàn),但dispatch_barrier更加方便;
dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"%@===TaskA",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"%@===TaskB",[NSThread currentThread]);
});
// async不會(huì)阻塞當(dāng)前線程(主線程)
dispatch_barrier_async(queue, ^{
NSLog(@"%@===Barrier",[NSThread currentThread]);
});
// sync會(huì)阻塞當(dāng)前隊(duì)列(主隊(duì)列)
// dispatch_barrier_sync(queue, ^{
// NSLog(@"%@===Barrier",[NSThread currentThread]);
// });
dispatch_async(queue, ^{
sleep(1);
NSLog(@"%@===TaskC",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"%@===TaskD",[NSThread currentThread]);
});
NSLog(@"%@===MainTask",[NSThread currentThread]);
<NSThread: 0x2816bbc40>{number = 1, name = main}===MainTask
<NSThread: 0x2816fe440>{number = 3, name = (null)}===TaskB
<NSThread: 0x2816877c0>{number = 4, name = (null)}===TaskA
<NSThread: 0x2816877c0>{number = 4, name = (null)}===Barrier
<NSThread: 0x2816fe440>{number = 3, name = (null)}===TaskD
<NSThread: 0x2816877c0>{number = 4, name = (null)}===TaskC
dispatch_barrier的使用有兩種方式
- dispatch_barrier_async
- dispatch_barrier_sync
async不會(huì)阻塞當(dāng)前隊(duì)列,sync同時(shí)會(huì)阻塞當(dāng)前隊(duì)列;如果以上代碼換成dispatch_barrier_sync,最終的結(jié)果將是MainTask會(huì)在Barrier任務(wù)后;
基于barrier的這種特性,很容易實(shí)現(xiàn)一個(gè)讀寫(xiě)鎖;柵欄內(nèi)為write,柵欄外為read;這樣同樣能實(shí)現(xiàn)讀任務(wù)能異步執(zhí)行,寫(xiě)任務(wù)只能同步執(zhí)行;同時(shí)在寫(xiě)操作時(shí),不允許讀操作;
dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 3; i ++) {
dispatch_async(queue, ^{
sleep(1);
NSLog(@"%@===read",[NSThread currentThread]);
});
}
for (int i = 0; i < 3; i ++) {
dispatch_barrier_async(queue, ^{
sleep(1);
NSLog(@"%@===write",[NSThread currentThread]);
});
}
for (int i = 0; i < 3; i ++) {
dispatch_async(queue, ^{
sleep(1);
NSLog(@"%@===read",[NSThread currentThread]);
});
}
<NSThread: 0x282a04880>{number = 4, name = (null)}===read
<NSThread: 0x282a13100>{number = 6, name = (null)}===read
<NSThread: 0x282a050c0>{number = 5, name = (null)}===read
<NSThread: 0x282a4ee40>{number = 1, name = main}===write
<NSThread: 0x282a4ee40>{number = 1, name = main}===write
<NSThread: 0x282a4ee40>{number = 1, name = main}===write
<NSThread: 0x282a050c0>{number = 5, name = (null)}===read
<NSThread: 0x282a13400>{number = 7, name = (null)}===read
<NSThread: 0x282a04880>{number = 4, name = (null)}===read

NSOperation相關(guān)
NSOperation是對(duì)GCD的封裝
最大并發(fā)數(shù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 最大并發(fā)數(shù)設(shè)置為1,queue內(nèi)任務(wù)同步執(zhí)行
queue.maxConcurrentOperationCount = 1;
設(shè)置柵欄
// similarly to the `dispatch_barrier_async` function.
[queue addBarrierBlock:^{
}];
設(shè)置依賴關(guān)系
使用場(chǎng)景:線程B必須要等線程A任務(wù)執(zhí)行完后才執(zhí)行,即線程A依賴線程B:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *taskA = [NSBlockOperation blockOperationWithBlock:^{
sleep(2);
NSLog(@"%@===TaskA",[NSThread currentThread]);
}];
NSBlockOperation *taskB = [NSBlockOperation blockOperationWithBlock:^{
sleep(.5);
NSLog(@"%@===TaskB",[NSThread currentThread]);
}];
[taskB addDependency:taskA];
[queue addOperation:taskA];
[queue addOperation:taskB];
<NSThread: 0x281af5bc0>{number = 6, name = (null)}===TaskA
<NSThread: 0x281af5bc0>{number = 6, name = (null)}===TaskB
自旋鎖、互斥鎖比較
前面我們介紹了自旋鎖、互斥鎖機(jī)制的不同,它們各有優(yōu)點(diǎn);實(shí)際開(kāi)發(fā)中的如何選擇呢?
適用自旋鎖的情況
- 線程等待時(shí)間比較短(這樣忙等的時(shí)間不會(huì)太長(zhǎng),不會(huì)有太大消耗)
- 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生
- CPU資源不緊張(自旋鎖比較耗CPU資源)
相反的,適用互斥鎖的情況
- 線程等待時(shí)間比較長(zhǎng)
- 加鎖的代碼(臨界區(qū))復(fù)雜,循環(huán)度大,或者有IO操作
- 加鎖的代碼(臨界區(qū))競(jìng)爭(zhēng)激烈
線程同步方案性能比較
這個(gè)直接引用大神的圖:

另外,os_unfair_lock鎖性能是最好的,可惜最低只支持iOS10;