細(xì)數(shù)iOS中的線程同步方案

多線程安全問(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;


完整demo

參考:
pthread的各種同步機(jī)制
不再安全的 OSSpinLock

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容