iOS 多線程

? 前言:這可能是史上最全面的一篇iOS 多線程博客了(王婆賣瓜一番??),從多線程的基本概念,進(jìn)程的概念,引出iOS中的四種多線程方案pthread、NSThread、NSOperation和GCD,每一部分都有詳細(xì)的代碼和解釋說明;在GCD中,引出同步、異步、串行隊列(包括主隊列)和并發(fā)隊列概念,并對他們的六種組合進(jìn)行詳細(xì)的代碼驗證和說明,把這些概念安排的明明白白,然后詳細(xì)的說明了GCD常見的其他用法;最后,對iOS中線程安全的方案進(jìn)行全方面的介紹說明并且配上示例代碼。好了,小編累的吐血去了...

一、多線程概念

1、多線程概念:

? 一個進(jìn)程中可以開啟多條線程,每條線程可以并行執(zhí)行不同的任務(wù)。多線程可以充分的利用多個CPU同時處理任務(wù),提高程序的執(zhí)行效率。

2、進(jìn)程概念:

? 進(jìn)程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,每個進(jìn)程之間是獨立的。而線程是應(yīng)用程序中一條任務(wù)的執(zhí)行路徑。

3、進(jìn)程和線程的關(guān)系:

? 1)一個進(jìn)程可以包含多個線程;

? 2)一個進(jìn)程中的所有任務(wù)都是在線程中執(zhí)行;

4、iOS多線程實現(xiàn)方案:

? 1)pthread:純C語言API,是一套通用的多線程API,適用于Linux、Unix、Windows等系統(tǒng),線程生命周期由程序員管理。在iOS實際開發(fā)中,使用較少。

? 2)NSThread:使用更加面向?qū)ο?,并可直接操作線程對象,線程生命周期由程序員管理,項目開發(fā)中使用較多;

? 3)NSOperation:基于GCD,使用更加面向?qū)ο?,線程生命周期系統(tǒng)自動管理,使用較多;

? 4)GCD:一套改進(jìn)的C語言多線程API,能充分利用設(shè)備的多核優(yōu)勢,線程生命周期系統(tǒng)自動管理,使用最多;

二、pThread

? 純C語言API,使用較為麻煩。

#import <pthread/pthread.h>

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self pthread];
}

//開啟新的線程執(zhí)行run方法
- (void)pthread {
    pthread_t thread;
    pthread_create(&thread, NULL, run, NULL);
}

void * run(void *param){
    NSLog(@"Thread = %@", [NSThread currentThread]);
    return NULL;
}

三、NSThread

1、NSThread的創(chuàng)建

? 一個NSThread對象就代表一個線程,有三種方式創(chuàng)建:

? 1)創(chuàng)建線程后需要start啟動線程;

? 2)創(chuàng)建線程后自動啟動線程;

? 3)隱式創(chuàng)建并啟動線程;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /*
     創(chuàng)建線程,線程對象(局部變量)系統(tǒng)會自己加持,在任務(wù)執(zhí)行完之前不會被銷毀
     當(dāng)線程任務(wù)執(zhí)行完畢,線程自動銷毀
     */
    // 1、使用start啟動線程
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"thread1"];
    thread1.name = @"thread1";
    [thread1 start];
    
    // 2、自動啟動線程
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"thread2"];
    
    // 3、隱式啟動線程
    [self performSelectorInBackground:@selector(run:) withObject:@"thread3"];
}

- (void)run:(NSString *)threadStr {
    if ([threadStr isEqualToString:@"thread1"]) {
        NSLog(@"thread1 = %@", [NSThread currentThread]);
    } else if ([threadStr isEqualToString:@"thread2"]) {
        NSLog(@"thread2 = %@", [NSThread currentThread]);
    } else if ([threadStr isEqualToString:@"thread3"]) {
        NSLog(@"thread3 = %@", [NSThread currentThread]);
    }
}

運行程序,打印結(jié)果:

thread1 = <NSThread: 0x60000081e180>{number = 6, name = thread1}
thread2 = <NSThread: 0x60000081e140>{number = 7, name = (null)}
thread3 = <NSThread: 0x60000081e040>{number = 8, name = (null)}
2、NSThread常用用法
//獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];

+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否為主線程
+ (BOOL)isMainThread; // 是否為主線程

//線程的名字,適用于第一種方式創(chuàng)建的線程(創(chuàng)建的時候返回NSThread的對象)
- (void)setName:(NSString *)n;
- (NSString *)name;
3、NSThread的線程通信

? 在開發(fā)中,我們經(jīng)常會在子線程進(jìn)行耗時操作,操作結(jié)束后再回到主線程去刷新 UI。這就涉及到了子線程和主線程之間的通信,常用兩個API如下:

/**
    回到主線程執(zhí)行任務(wù),RunLoop模式默認(rèn)是kCFRunLoopCommonModes
@param aSelector:選擇器(方法)
@param arg:傳遞的參數(shù)
@param wait:是否等待任務(wù)執(zhí)行完畢
*/
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

/**
    在指定線程中執(zhí)行任務(wù),RunLoop模式默認(rèn)是kCFRunLoopCommonModes
@param aSelector:選擇器(方法)
@param thr:指定的線程
@param arg:傳遞的參數(shù)
@param wait:是否等待任務(wù)執(zhí)行完畢
*/
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

模擬子線程下載,主線程刷新UI代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 創(chuàng)建新線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runTestThread) object:nil];
    [thread start];
    // 注意:這里waitUntilDone只能是NO,不然程序閃退
    [self performSelector:@selector(runTestThread) onThread:thread withObject:nil waitUntilDone:NO];
}

// 子線程,執(zhí)行耗時操作
- (void)runTestThread {
    NSLog(@"runTestThread = %@", [NSThread currentThread]);
    sleep(3);
    // waitUntilDone如果是YES,那么會阻塞當(dāng)前線程,waitUntilDone會最后打印
    [self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:NO];
    NSLog(@"waitUntilDone...");
}

// 主線程,刷新UI
- (void)runMainThread {
    NSLog(@"runMainThread = %@", [NSThread currentThread]);
}

程序運行打印結(jié)果:

runTestThread = <NSThread: 0x600001fccc40>{number = 6, name = (null)}
waitUntilDone... // waitUntilDone如果是YES,那么會阻塞當(dāng)前線程,waitUntilDone會最后打印
runMainThread = <NSThread: 0x600001fa2180>{number = 1, name = main}

四、NSOperation

? NSOperation和NSOperationQueue是對GCD的一層封裝,NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類NSInvocationOperation和NSBlockOperation。

1、NSInvocationOperation

? NSInvocationOperation代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /**
     默認(rèn)情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作
     只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作
     */
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [op start];
}

- (void)run {
    NSLog(@"mainThread = %@", [NSThread currentThread]);
}

運行結(jié)果:

// 說明還是當(dāng)前的線程,并沒有開啟新線程
mainThread = <NSThread: 0x6000036ed600>{number = 1, name = main}    
2、NSBlockOperation

? NSBlockOperation代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 通過一個block創(chuàng)建NSBlockOperation實例
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        /**
         如果NSBlockOperation沒有再添加block,即只有一個操作數(shù),那么這里肯定是主線程
         如果NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作,那么實際測試,這里不一定是主線程
         */
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    }];
    
    [op addExecutionBlock:^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    }];
    
    [op addExecutionBlock:^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    }];
    // 開始
    [op start];
}

運行結(jié)果:Thread1、Thread2和Thread3當(dāng)中肯定會有一個是主線程。

Thread1 = <NSThread: 0x600002e32f40>{number = 4, name = (null)} //不一定是主線程
Thread2 = <NSThread: 0x600002e39380>{number = 6, name = (null)}
Thread3 = <NSThread: 0x600002e64680>{number = 1, name = main}
3、NSOperationQueue

? NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的,如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統(tǒng)會自動異步執(zhí)行操作。

? 1)NSInvocationOperation與NSOperationQueue組合:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1、創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2、創(chuàng)建NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run1) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
    
    // 3、添加任務(wù)到隊列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
}

- (void)run1 {
    NSLog(@"Thread1 = %@", [NSThread currentThread]);
}

- (void)run2 {
    NSLog(@"Thread2 = %@", [NSThread currentThread]);
}

運行結(jié)果:Thread1和Thread2都是在子線程,而不是在主線程,是異步的。

Thread1 = <NSThread: 0x60000111e440>{number = 5, name = (null)}
Thread2 = <NSThread: 0x600001115840>{number = 6, name = (null)}

? 2)NSBlockOperation與NSOperationQueue組合:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1、創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 2、創(chuàng)建NSBlockOperation
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    }];
    
    [op1 addExecutionBlock:^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    }];

    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Thread4 = %@", [NSThread currentThread]);
    }];
    
    // 3、添加任務(wù)到隊列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}

運行結(jié)果:Thread1、Thread2、Thread3、Thread4都是子線程,所以添加到隊列之后,是異步的。

Thread4 = <NSThread: 0x6000020d9c80>{number = 6, name = (null)}
Thread2 = <NSThread: 0x6000020a3140>{number = 5, name = (null)}
Thread3 = <NSThread: 0x6000020dcec0>{number = 7, name = (null)}
Thread1 = <NSThread: 0x6000020bc740>{number = 4, name = (null)}
4、控制NSOperationQueue是串行隊列還是并發(fā)隊列

? 可以通過設(shè)置maxConcurrentOperationCount的值來選擇串行隊列還是并發(fā)隊列。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    /**
     maxCount等于-1:默認(rèn)值,也就是不限制最大并發(fā)數(shù),添加的operation都是異步的
     maxCount等于0:不執(zhí)行operation
     maxCount等于1:在子線程同步執(zhí)行operation,也就是串行隊列
     maxCount大于1:在指定數(shù)量的線程內(nèi)異步處理operation
     maxCount為負(fù)數(shù),且不等于-1:程序拋出異常,count cannot be negative
     */
    queue.maxConcurrentOperationCount = -1;
    
    // 添加操作
    [queue addOperationWithBlock:^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
        // 切換到主線程
        NSOperationQueue *main = [NSOperationQueue mainQueue];
        [main addOperationWithBlock:^{
            NSLog(@"Thread2 = %@", [NSThread currentThread]);
        }];
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    }];
}

運行結(jié)果:

Thread1 = <NSThread: 0x600002d93440>{number = 4, name = (null)}
Thread3 = <NSThread: 0x600002dd1fc0>{number = 6, name = (null)}
Thread2 = <NSThread: 0x600002d86200>{number = 1, name = main}
5、NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序

? 比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,代碼如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    }];
    [op2 addExecutionBlock:^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    }];
    
    /**
     1、[op1 addDependency:op2]:op1依賴于op2執(zhí)行完成
     2、如果同時設(shè)置op1依賴于op2,op2依賴于op1,會造成死鎖,不會執(zhí)行任務(wù)了
     3、任務(wù)可以跨隊列依賴,在不同隊列里面的任務(wù)也可以相互依賴
     */
    [op1 addDependency:op2];
    [queue addOperation:op1];
    [queue addOperation:op2];
}

運行結(jié)果:先執(zhí)行op2(Thread2、Thread3),后執(zhí)行op1(Thread1)。

Thread3 = <NSThread: 0x600002cb8ac0>{number = 7, name = (null)}
Thread2 = <NSThread: 0x600002cbcc00>{number = 6, name = (null)}
Thread1 = <NSThread: 0x600002cbcc00>{number = 6, name = (null)}

五、GCD

? Grand Central Dispatch (GCD)是Apple開發(fā)的一個多核編程的較新的解決方法,它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對稱多處理系統(tǒng),它是一個在線程池模式的基礎(chǔ)上執(zhí)行的并行任務(wù),GCD是一個替代諸如NSThread等技術(shù)的很高效和強大的技術(shù)。

? GCD 會自動利用更多的 CPU 內(nèi)核(比如雙核、四核),GCD 會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼。

? GCD兩個核心概念,任務(wù)和隊列,任務(wù)包括(同步執(zhí)行任務(wù)、異步執(zhí)行任務(wù)),隊列包括(串行隊列、并發(fā)隊列),隊列采用 FIFO(First In First Out)的原則,即先進(jìn)先出原則。

1、同步執(zhí)行、異步執(zhí)行

? 同步執(zhí)行與異步執(zhí)行的區(qū)別:是否等待隊列的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力。

? 1)同步執(zhí)行(sync):同步添加任務(wù)到指定的隊列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行。只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力。

? 2)異步執(zhí)行(async):異步添加任務(wù)到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務(wù)。可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力。

? 注意:異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程。這跟任務(wù)所指定的隊列類型有關(guān)。

2、串行隊列、并發(fā)隊列

? 串行隊列和并發(fā)隊列的區(qū)別:執(zhí)行順序不同,以及開啟線程數(shù)不同。

? 1)串行隊列(Serial Dispatch Queue):讓任務(wù)一個接著一個地執(zhí)行(最多創(chuàng)建一個線程)。dispatch_get_main_queue() 主隊列就是一個串行隊列。

? 2)并發(fā)隊列(Concurrent Dispatch Queue):可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(可以開啟多個線程)。dispatch_get_global_queue(0, 0) 全局隊列就是并發(fā)隊列。

? 注意:并發(fā)隊列 的并發(fā)功能只有在異步(dispatch_async)方法下才有效。

3、經(jīng)典各種組合模式

? 本來同步、異步、串行、并發(fā)有四種組合,但是當(dāng)前代碼默認(rèn)放在主隊列中,全局并發(fā)隊列可以作為普通并發(fā)隊列來使用,所以主隊列很有必要專門來研究一下,所以我們就有六種組合模式了。

? 1)同步執(zhí)行 + 串行隊列:沒有開啟新線程,串行執(zhí)行任務(wù)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    });
}

運行結(jié)果:線程還是main主線程,沒有開啟新線程,Thread1、Thread2、Thread3按順序執(zhí)行。

Thread1 = <NSThread: 0x600003627900>{number = 1, name = main}
Thread2 = <NSThread: 0x600003627900>{number = 1, name = main}
Thread3 = <NSThread: 0x600003627900>{number = 1, name = main}

? 2)同步執(zhí)行 + 并發(fā)隊列:沒有開啟新線程,串行執(zhí)行任務(wù)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    });
}

運行結(jié)果:線程還是main主線程,沒有開啟新線程,Thread1、Thread2、Thread3按順序執(zhí)行。

Thread1 = <NSThread: 0x6000011ea140>{number = 1, name = main}
Thread2 = <NSThread: 0x6000011ea140>{number = 1, name = main}
Thread3 = <NSThread: 0x6000011ea140>{number = 1, name = main}

? 3)異步執(zhí)行 + 串行隊列:有開啟新線程(1條),串行執(zhí)行任務(wù)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    });
}

運行結(jié)果:有開啟一條新線程,Thread1、Thread2、Thread3按順序執(zhí)行。

Thread1 = <NSThread: 0x6000006a4340>{number = 6, name = (null)}
Thread2 = <NSThread: 0x6000006a4340>{number = 6, name = (null)}
Thread3 = <NSThread: 0x6000006a4340>{number = 6, name = (null)}

? 4)異步執(zhí)行 + 并發(fā)隊列:有開啟新線程(多條),并發(fā)執(zhí)行任務(wù)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    });
}

運行結(jié)果:有開啟多條新線程,Thread1、Thread2、Thread3是并發(fā)執(zhí)行的。

Thread2 = <NSThread: 0x600003c65800>{number = 7, name = (null)}
Thread1 = <NSThread: 0x600003c74840>{number = 6, name = (null)}
Thread3 = <NSThread: 0x600003c45940>{number = 5, name = (null)}

? 5)同步執(zhí)行+主隊列:死鎖卡住不執(zhí)行。其實如果在串行隊列中嵌套同步使用串行隊列,也會發(fā)生死鎖,原理和這個類似。所以項目中數(shù)據(jù)庫處理FMDataQueue嵌套使用時,需要注意死鎖問題。

? 如果不是在主線程執(zhí)行同步執(zhí)行+主隊列呢,那么不會發(fā)生死鎖(讀者可以自己代碼測試驗證)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /**
    dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{ //異步串行,創(chuàng)建一條新線程
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
        dispatch_sync(serialQueue, ^{ //嵌套同步串行,發(fā)生死鎖
            NSLog(@"Thread2 = %@", [NSThread currentThread]);
        });
    });
    */
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_sync(mainQueue, ^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    });
    dispatch_sync(mainQueue, ^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    });
    dispatch_sync(mainQueue, ^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    });
}

運行結(jié)果:程序死鎖崩潰,原因是默認(rèn)viewDidLoad被添加到主隊列中(先運行完viewDidLoad,后運行添加的任務(wù)),然后又同步添加Thread1任務(wù)到主隊列中(先運行Thread1任務(wù),后運行viewDidLoad任務(wù)),造成任務(wù)相互等待卡死,程序死鎖崩潰。

? 6)異步執(zhí)行+主隊列:沒有開啟新線程,串行執(zhí)行任務(wù)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
    });
    dispatch_async(mainQueue, ^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    });
    dispatch_async(mainQueue, ^{
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    });
}

運行結(jié)果:線程還是main主線程,沒有開啟新線程,Thread1、Thread2、Thread3按順序執(zhí)行。

Thread1 = <NSThread: 0x6000006ac0c0>{number = 1, name = main}
Thread2 = <NSThread: 0x6000006ac0c0>{number = 1, name = main}
Thread3 = <NSThread: 0x6000006ac0c0>{number = 1, name = main}
4、GCD線程間的通信

? 在項目開發(fā)中,一般耗時的操作在別的線程處理,然后在主線程刷新UI,GCD線程間的通信如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2); //模擬耗時操作
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Thread2 = %@", [NSThread currentThread]);
        });
    });
}

運行結(jié)果:在子線程執(zhí)行耗時操作,再回到主線程刷新UI。

Thread1 = <NSThread: 0x600002189400>{number = 5, name = (null)}
Thread2 = <NSThread: 0x6000021dc0c0>{number = 1, name = main}
5、GCD其他的常見用法

? 1)柵欄方法(dispatch_barrier_async)

? 我們有時需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作,這就需要用到dispatch_barrier_async 方法在兩個操作組間形成柵欄。NSOperation的 addDependency 也是這個效果。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 創(chuàng)建并發(fā)隊列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        sleep(1); //模擬耗時操作
        NSLog(@"Thread1 = %@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"Thread2 = %@",[NSThread currentThread]);
    });
    
    // 添加?xùn)艡?    dispatch_barrier_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"Thread3 = %@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"Thread4 = %@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"Thread5 = %@",[NSThread currentThread]);
    });
}

運行結(jié)果:先執(zhí)行第一組任務(wù)(Thread1、Thread2),然后再執(zhí)行柵欄的第二組任務(wù)(Thread3、Thread4、Thread5)。

Thread2 = <NSThread: 0x6000012ad780>{number = 4, name = (null)}
Thread1 = <NSThread: 0x60000129c540>{number = 7, name = (null)}
Thread3 = <NSThread: 0x6000012ad780>{number = 4, name = (null)}
Thread5 = <NSThread: 0x6000012af680>{number = 6, name = (null)}
Thread4 = <NSThread: 0x6000012ad780>{number = 4, name = (null)}

? 2)延時方法(dispatch_after)

? 項目中我們有時需要幾秒之后再執(zhí)行某個方法,那么可以使用GCD的延時方法,而不用創(chuàng)建一個定時器來處理。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"Thread1 = %@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0 秒后異步追加任務(wù)代碼到主隊列,并開始執(zhí)行,如果主線程被阻塞,時間上不會非常準(zhǔn)確
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
    });
    sleep(5); //阻塞主線程
    NSLog(@"Thread3 = %@", [NSThread currentThread]);
}

運行結(jié)果:先執(zhí)行Thread1,5s之后執(zhí)行Thread3,當(dāng)前主隊列執(zhí)行完畢,然后執(zhí)行dispatch_after添加的任務(wù)Thread2。至于為什么Thread3、Thread2時間接近,而不是相差2s,因為執(zhí)行完Thread1的2s之后,Thread2添加到主隊列,正在等待主隊列執(zhí)行完畢。

2019-10-24 10:03:05.638019 Thread1 = <NSThread: 0x6000005be200>{number = 1, name = main}
2019-10-24 10:03:10.639501 Thread3 = <NSThread: 0x6000005be200>{number = 1, name = main}
2019-10-24 10:03:10.658852 Thread2 = <NSThread: 0x6000005be200>{number = 1, name = main}

假如注釋掉 sleep(5),那么運行結(jié)果:主隊列沒被阻塞,Thread2任務(wù)基本2s之后就會執(zhí)行。

2019-10-24 10:10:55.653691 Thread1 = <NSThread: 0x600000c44680>{number = 1, name = main}
2019-10-24 10:10:55.653898 Thread3 = <NSThread: 0x600000c44680>{number = 1, name = main}
2019-10-24 10:10:57.654041 Thread2 = <NSThread: 0x600000c44680>{number = 1, name = main}

? 3)執(zhí)行一次(dispatch_once)

? 項目開發(fā)中,我們經(jīng)常使用單例模式,那么就對 dispatch_once 很熟悉,代碼只運行一次。并且即使在多線程的環(huán)境下,dispatch_once 也可以保證線程安全。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self executeOnce]; //先執(zhí)行一次
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
        [self executeOnce];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
        [self executeOnce];
    });
}

- (void)executeOnce {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只會執(zhí)行一次
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
    });
}

運行結(jié)果:Thread3 只被執(zhí)行一次,并且是線程安全的。

Thread3 = <NSThread: 0x600002f21dc0>{number = 1, name = main}
Thread1 = <NSThread: 0x600002f70300>{number = 5, name = (null)}
Thread2 = <NSThread: 0x600002f6c200>{number = 3, name = (null)}

? 4)快速迭代方法(dispatch_apply)

? 通常我們會用 for 循環(huán)遍歷,但是 GCD 給我們提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次數(shù)將指定的任務(wù)追加到指定的隊列中,并等待全部任務(wù)執(zhí)行結(jié)束。如果是在串行隊列中使用dispatch_apply,那么就和 for 循環(huán)一樣,按順序同步執(zhí)行,所以實際使用的時候一般用并發(fā)隊列。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0); //全局并發(fā)隊列
    NSLog(@"dispatch_apply begin");
    dispatch_apply(5, queue, ^(size_t index) {
        sleep(1);
        NSLog(@"Thread_%ld = %@", index, [NSThread currentThread]);
    });
    NSLog(@"dispatch_apply end");
}

運行結(jié)果:先異步執(zhí)行完所有的任務(wù),最后執(zhí)行dispatch_apply end。

dispatch_apply begin
Thread_0 = <NSThread: 0x6000028dc040>{number = 4, name = (null)}
Thread_2 = <NSThread: 0x6000028f4700>{number = 6, name = (null)}
Thread_1 = <NSThread: 0x6000028cdc40>{number = 5, name = (null)}
Thread_3 = <NSThread: 0x600002880040>{number = 1, name = main}
Thread_4 = <NSThread: 0x6000028dc040>{number = 4, name = (null)}
dispatch_apply end

? 5)隊列組(dispatch_group)

? 有時候我們會有這樣的需求:分別異步執(zhí)行2個耗時任務(wù),然后當(dāng)2個耗時任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù),這時候我們可以用到 GCD 的隊列組。項目中常見使用場景有:請求多個接口數(shù)據(jù)返回后,再統(tǒng)一進(jìn)行刷新;下載完多張圖片后,再統(tǒng)一進(jìn)行合并繪制等。

? 調(diào)用隊列組的 dispatch_group_async 先把任務(wù)放到隊列中,然后將隊列放入隊列組中?;蛘呤褂藐犃薪M的 dispatch_group_enter、dispatch_group_leave 組合來實現(xiàn) dispatch_group_async。

? 調(diào)用隊列組的 dispatch_group_notify 回到指定線程執(zhí)行任務(wù)?;蛘呤褂?dispatch_group_wait 回到當(dāng)前線程繼續(xù)向下執(zhí)行(會阻塞當(dāng)前線程)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_group_t group =  dispatch_group_create();
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, globalQueue, ^{
        sleep(1);
        NSLog(@"Thread1 = %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, globalQueue, ^{
        sleep(1);
        NSLog(@"Thread2 = %@",[NSThread currentThread]);
    });
    /**
     通過enter、leave實現(xiàn)
     dispatch_group_enter:標(biāo)志著一個任務(wù)追加到 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) +1
     dispatch_group_leave:標(biāo)志著一個任務(wù)離開了 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) -1
     當(dāng) group 中未執(zhí)行任務(wù)數(shù)為0的時候,才會使 dispatch_group_wait 解除阻塞,以及執(zhí)行追加到 dispatch_group_notify 中的任務(wù)
     */
    dispatch_group_enter(group);
    dispatch_async(globalQueue, ^{
        sleep(1);
        NSLog(@"Thread3 = %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    // 通知
    dispatch_group_notify(group, mainQueue, ^{
        // 等前面的異步任務(wù)1、任務(wù)2都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務(wù)
        sleep(1);
        NSLog(@"Thread4 = %@",[NSThread currentThread]);
    });
    
    NSLog(@"dispatch_group_wait 之前??");
    // 設(shè)置DISPATCH_TIME_FOREVER會一直等待group結(jié)束,會阻塞當(dāng)前線程,如果設(shè)置為DISPATCH_TIME_NOW就不會等待
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group 執(zhí)行完畢");
}

運行結(jié)果:先異步執(zhí)行g(shù)roup任務(wù)(Thread1、Thread2、Thread3),group執(zhí)行完畢后,再執(zhí)行nofity的任務(wù)。

dispatch_group_wait 之前??
Thread2 = <NSThread: 0x600003ab8b00>{number = 6, name = (null)}
Thread3 = <NSThread: 0x600003abc600>{number = 4, name = (null)}
Thread1 = <NSThread: 0x600003a8c540>{number = 7, name = (null)}
group 執(zhí)行完畢
Thread4 = <NSThread: 0x600003ae2140>{number = 1, name = main}

? 6)信號量(dispatch_semaphore)

? 項目開發(fā)中,有時候有這樣的需求:異步執(zhí)行耗時任務(wù),并使用異步執(zhí)行的結(jié)果進(jìn)行一些額外的操作。比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通過引入信號量的方式,等待異步執(zhí)行任務(wù)結(jié)果,獲取到 tasks,然后再返回該 tasks。

? GCD 中的信號量是指 Dispatch Semaphore,是持有計數(shù)的信號。在 Dispatch Semaphore 中,使用計數(shù)來完成這個功能,計數(shù)小于 0 時等待,不可通過。計數(shù)為 0 或大于 0 時,不等待,可通過。信號量主要用于:

? a、保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù);

? b、保證線程安全,為線程加鎖;

// AFNetworking 部分源碼
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

我們自己用信號量實現(xiàn)線程同步

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"dispatch_semaphore begin");
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    /**
     創(chuàng)建信號量
     小于0:程序閃退
     等于0:正常使用,會同步執(zhí)行代碼
     大于0:不會同步執(zhí)行
     */
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    /**
     dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加 1
     dispatch_semaphore_wait:可以使總信號量減 1,信號總量小于 0 時就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行。
     */
    dispatch_async(globalQueue, ^{
        sleep(1);
        NSLog(@"Thread1 = %@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore); //信號量 +1
    });
    
    NSLog(@"dispatch_semaphore_wait 之前??");
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //信號量 -1
    NSLog(@"dispatch_semaphore end");
}

運行結(jié)果:semaphore初始創(chuàng)建時計數(shù)為 0,異步將Thread1任務(wù)添加到全局并發(fā)隊列,不做等待,接著執(zhí)行 dispatch_semaphore_wait 方法,semaphore 減 1,此時 semaphore == -1,當(dāng)前線程進(jìn)入等待狀態(tài)。

? 然后,異步任務(wù) 1 開始執(zhí)行。任務(wù) 1 執(zhí)行到 dispatch_semaphore_signal 之后,總信號量加 1,此時 semaphore == 0,正在被阻塞的線程(主線程)恢復(fù)繼續(xù)執(zhí)行,最后打印end。

dispatch_semaphore begin
dispatch_semaphore_wait 之前??
Thread1 = <NSThread: 0x6000021588c0>{number = 4, name = (null)}
dispatch_semaphore end

我們自己用信號量實現(xiàn) semaphore 加鎖

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.ticketCount = 10;
    /**
    注意:這里參數(shù)是1
            信號量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量
            信號量的初始值為1,代表同時只允許1條線程訪問資源,保證線程同步
    */
    self.semaphoreLock = dispatch_semaphore_create(1); //
    
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_async(globalQueue, ^{ //第一條線程
        [self sellTickets];
    });
    dispatch_async(globalQueue, ^{ //第二條線程
        [self sellTickets];
    });
    dispatch_async(globalQueue, ^{ //第三條線程
        [self sellTickets];
    });
}

- (void)sellTickets {
    while (1) { //一直運行,直到break退出
        // 等待信號量,類似于加鎖,此時信號量減一,如果信號量減到小于0,那么就等待一個新的信號量發(fā)送
        dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);

        if (self.ticketCount > 0) {  //如果還有票,繼續(xù)售賣
            self.ticketCount --;
            NSLog(@"剩余票數(shù) = %ld, 線程 = %@", self.ticketCount, [NSThread currentThread]);
            [NSThread sleepForTimeInterval:0.2]; //模擬耗時操作
        } else { //如果已賣完,關(guān)閉售票窗口
            NSLog(@"火車票售完");
            // 發(fā)送一個信號量,類似于解鎖
            dispatch_semaphore_signal(self.semaphoreLock);
            break;
        }
        // 發(fā)送一個信號量,類似于解鎖
        dispatch_semaphore_signal(self.semaphoreLock);
    }
}

運行結(jié)果:ticketCount 按照順序依次遞減1,“火車票售完”打印三次是因為有三條線程運行。

? 分析過程:首先三條線程都并發(fā)執(zhí)行sellTickets方法,最快的一條線程首先執(zhí)行dispatch_semaphore_wait,信號量減一,此時信號量為0,該條線程繼續(xù)執(zhí)行wait之后的代碼,而其他較慢的兩條線程進(jìn)行等待新的信號量出現(xiàn),較快的一條線程賣票之后,發(fā)送一個信號量,那么較慢的兩條線程其中的一條執(zhí)行wait之后的代碼,如此循環(huán),保證一個時間點只有一條線程進(jìn)行賣票,從而保證線程安全。

剩余票數(shù) = 9, 線程 = <NSThread: 0x600003e0e840>{number = 6, name = (null)}
剩余票數(shù) = 8, 線程 = <NSThread: 0x600003e6da40>{number = 4, name = (null)}
剩余票數(shù) = 7, 線程 = <NSThread: 0x600003e6d740>{number = 5, name = (null)}
剩余票數(shù) = 6, 線程 = <NSThread: 0x600003e0e840>{number = 6, name = (null)}
剩余票數(shù) = 5, 線程 = <NSThread: 0x600003e6da40>{number = 4, name = (null)}
剩余票數(shù) = 4, 線程 = <NSThread: 0x600003e6d740>{number = 5, name = (null)}
剩余票數(shù) = 3, 線程 = <NSThread: 0x600003e0e840>{number = 6, name = (null)}
剩余票數(shù) = 2, 線程 = <NSThread: 0x600003e6da40>{number = 4, name = (null)}
剩余票數(shù) = 1, 線程 = <NSThread: 0x600003e6d740>{number = 5, name = (null)}
剩余票數(shù) = 0, 線程 = <NSThread: 0x600003e0e840>{number = 6, name = (null)}
火車票售完
火車票售完
火車票售完

如果把信號量的代碼注釋,運行結(jié)果:剩余票數(shù)順序明顯不對,數(shù)據(jù)錯亂。

剩余票數(shù) = 8, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
剩余票數(shù) = 7, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 9, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 6, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 6, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
剩余票數(shù) = 5, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 4, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 4, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 4, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
剩余票數(shù) = 3, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 3, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 3, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
剩余票數(shù) = 2, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 1, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 0, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
火車票售完
火車票售完
火車票售完

六、線程安全

? 當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題。線程安全問題:一般使用線程同步技術(shù),同步技術(shù)有加鎖、串行隊列、信號量等。

? 串行隊列,上文已經(jīng)有介紹,比如項目中FMDBQueue,就是在串行隊列中同步操作數(shù)據(jù)庫,從而保證線程安全。

? 信號量,上文已經(jīng)有介紹,其實類似于互斥鎖。

? 下面主要講iOS中常見的鎖:

? 從大的方向講有兩種鎖:自旋鎖、互斥鎖。

? 自旋鎖:線程反復(fù)檢查鎖變量是否可用,是一種忙等狀態(tài),自旋鎖避免了進(jìn)程上下文的調(diào)度開銷,因此對于線程只會阻塞很短時間的場合是有效的。自旋鎖的性能高于互斥鎖,因為響應(yīng)速度快,但是可能發(fā)生優(yōu)先級反轉(zhuǎn)問題(如果等待鎖的線程優(yōu)先級較高,它會一直占用著CPU資源,優(yōu)先級低的線程就無法釋放鎖)。常見的自旋鎖包括:OSSpinLock、atomic等。

? 互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會進(jìn)入休眠狀態(tài)等待鎖,因此適用于線程阻塞時間較長的場合。常見的互斥鎖包括:os_unfair_lock、pthread_mutex、dispatch_semaphore、@synchronized,其中pthread_mutex又衍生出NSLock、NSCondition、NSConditionLock、NSRecursiveLock,更加面向?qū)ο蟮逆i。

至于鎖的性能參考這個博客傳送門,我只把測試結(jié)果放出來。

各種鎖的性能測試結(jié)果

1、OSSpinLock

? 自旋鎖OSSpinLock,已經(jīng)在iOS10被蘋果棄用,因為它存在優(yōu)先級反轉(zhuǎn)的問題。

#import <libkern/OSAtomic.h>

OSSpinLock lock = OS_SPINLOCK_INIT; //創(chuàng)建鎖
OSSpinLockTry(&lock); //嘗試加鎖,加鎖失敗返回NO,成功返回YES
OSSpinLockLock(&lock); //加鎖
// [self sellTickets]; 
OSSpinLockUnlock(&lock); //解鎖
2、os_unfair_lock

? 這是蘋果iOS10之后推出的新的取代OSSpinLock的鎖。雖然是替代OSSpinLock的,但os_unfair_lock并不是自旋鎖,而是互斥鎖。

#import <os/lock.h>

os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //創(chuàng)建鎖
os_unfair_lock_trylock(&lock); //嘗試加鎖,加鎖失敗返回NO,成功返回YES
os_unfair_lock_lock(&lock); //加鎖
// [self sellTickets];
os_unfair_lock_unlock(&lock); //解鎖
3、dispatch_semaphore

? 雖然上面有示例代碼,但是這里也還是把思路寫出來進(jìn)行對比學(xué)習(xí)。

dispatch_semaphore_t lock =  dispatch_semaphore_create(1); //創(chuàng)建鎖
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); //加鎖
// [self sellTickets];
dispatch_semaphore_signal(lock); //解鎖
4、pthread_mutex

? pthread_mutex是c底層的線程鎖,關(guān)于pthread的各種同步機制,感興趣的可以看看這篇文章pthread的各種同步機制,可謂講的相當(dāng)全面了。

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //創(chuàng)建鎖
pthread_mutex_trylock(&lock); //嘗試加鎖,加鎖失敗返回NO,成功返回YES
pthread_mutex_lock(&lock); //加鎖
// [self sellTickets];
pthread_mutex_unlock(&lock); //解鎖

在YYKit的YYMemoryCache中,截取部分相關(guān)代碼供大家參考。

pthread_mutex_t _lock; //聲明
pthread_mutex_init(&_lock, NULL); //初始化

pthread_mutex_lock(&_lock); //加鎖
NSUInteger count = _lru->_totalCount;
pthread_mutex_unlock(&_lock); //解鎖

pthread_mutex_destroy(&_lock); //銷毀鎖
5、NSLock、NSCondition、NSConditionLock、NSRecursiveLock

? 以上四種鎖全部是基于pthread_mutex封裝的面向?qū)ο蟮逆i。這幾種鎖都遵守NSLocking協(xié)議,此協(xié)議中提供了加鎖 lock 和解鎖 unlock 方法。

? 1)NSLock

? 比pthread_mutex更加面向?qū)ο?,使用也很簡單?/p>

NSLock *lock = [NSLock new]; //創(chuàng)建鎖
[lock tryLock]; //嘗試加鎖,加鎖失敗返回NO,成功返回YES
[lock lock]; //加鎖
// [self sellTickets];
[lock unlock]; //解鎖

在AFNetworking里的AFURLSessionManager.m文件里,截取一部分NSLock的用法供參考。

@property (readwrite, nonatomic, strong) NSLock *lock;

self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;

[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];

2)NSCondition

條件鎖,它的使用和dispatch_semaphore有異曲同工之妙,API有wait和signal。

- (void)wait;   // 線程等待
- (BOOL)waitUntilDate:(NSDate *)limit;  // 設(shè)置線程等待時間,過了這個時間就會自動執(zhí)行后面的代碼
- (void)signal; // 喚醒一個設(shè)置為wait等待的線程
- (void)broadcast;  // 喚醒所有設(shè)置為wait等待的線程
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 比如刪除數(shù)組中的所有元素,等數(shù)組有元素時再去刪除
    NSCondition *lock = [[NSCondition alloc] init];
    NSMutableArray *array = [[NSMutableArray alloc] init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{ //線程1
        [lock lock];
        while (!array.count) {
            [lock wait]; //等待
        }
        [array removeAllObjects];
        NSLog(@"array removeAllObjects");
        [lock unlock];
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{ //線程2
        sleep(1); //以保證讓線程2的代碼后執(zhí)行
        [array addObject:@"Hello world!"];
        NSLog(@"array addObject:Hello world!");
        [lock signal]; //發(fā)送信號
    });
}

3)NSConditionLock

條件鎖,可以設(shè)置更多的條件。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化NSConditionLock,并且condition設(shè)置為0
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        if([lock tryLockWhenCondition:0]){ //當(dāng)condition為0時,嘗試加鎖
            NSLog(@"Thread1 = %@", [NSThread currentThread]);
            [lock unlockWithCondition:1]; //解鎖,并且設(shè)置condition為1
        }else{
            NSLog(@"失敗");
        }
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lockWhenCondition:3];
        NSLog(@"Thread2 = %@", [NSThread currentThread]);
        [lock unlockWithCondition:2];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lockWhenCondition:1];
        NSLog(@"Thread3 = %@", [NSThread currentThread]);
        [lock unlockWithCondition:3];
    });
}

運行結(jié)果:condition初始化是0,所以先加鎖執(zhí)行Thread1,然后解鎖并且設(shè)置condition為1;接著當(dāng)condition為1時,先加鎖執(zhí)行Thread3,然后解鎖并且設(shè)置condition為3;最后類似的,執(zhí)行Thread2。

根據(jù)NSConditionLock的這種特性,可以用來做多線程任務(wù)之間的依賴。

Thread1 = <NSThread: 0x60000328c640>{number = 7, name = (null)}
Thread3 = <NSThread: 0x6000032b62c0>{number = 4, name = (null)}
Thread2 = <NSThread: 0x6000032b65c0>{number = 5, name = (null)}

? 4)NSRecursiveLock

? 遞歸鎖,遞歸鎖有一個特點,就是同一個線程可以加鎖N次而不會引發(fā)死鎖。

NSRecursiveLock *lock = [NSRecursiveLock new];
[lock tryLock];
[lock lock];
// [self sellTickets];
[lock unlock];

當(dāng)然嘍,pthread_mutex鎖也支持遞歸,只需要設(shè)置PTHREAD_MUTEX_RECURSIVE即可。

pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //綁定遞歸type
pthread_mutex_init(&lock, &attr); //初始化遞歸鎖
pthread_mutexattr_destroy(&attr); //銷毀attr

pthread_mutex_lock(&lock); //加鎖
// [self sellTickets];
pthread_mutex_unlock(&lock); //解鎖
pthread_mutex_destroy(&lock); //結(jié)束時銷毀鎖
6、pthread_rwlock

? 讀寫鎖,經(jīng)常用于文件等數(shù)據(jù)的讀寫操作,可以保證讀寫的正確性。如果需要實現(xiàn)“多讀單寫”功能,也可以用GCD的柵欄方法。

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; //創(chuàng)建讀寫鎖
pthread_rwlock_rdlock(&rwlock); //讀鎖
pthread_rwlock_wrlock(&rwlock); //寫鎖
pthread_rwlock_unlock(&rwlock); //解鎖
7、@synchronized

? @synchronized,雖然在所有鎖里面性能最差,但是代碼最簡單,所以項目實際使用很常見。

@synchronized (self) {
    // [self sellTickets];
}

總結(jié)一下:

? 1)大部分鎖都是先創(chuàng)建鎖,然后加鎖、解鎖等流程;

? 2)如果對性能有要求,優(yōu)先使用dispatch_semaphore或者pthread_mutex,如果對性能無要求,一般使用最簡單的@synchronized即可。

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

相關(guān)閱讀更多精彩內(nèi)容

  • 系列文章: 多線程 多線程 pthread、NSThread 多線程 GCD 多線程 NSOperation 多線...
    林安530閱讀 467評論 0 0
  • 一、什么是GCD 全稱是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”,純C語言,提供了...
    懂得_sniper閱讀 2,035評論 0 0
  • 一、前言 本篇博文介紹的是iOS中常用的幾個多線程技術(shù): NSThread GCD NSOperation 由于a...
    和玨貓閱讀 664評論 0 1
  • 多線程的四種解決方案:pthread,NSThread,GCD,NSOperation 一、多線程的基本概念進(jìn)程:...
    陽明AI閱讀 564評論 0 3
  • 一.進(jìn)程&線程 進(jìn)程:是程序執(zhí)行過程中分配和管理資源的一個基本單位。 線程:是程序執(zhí)行過程中任務(wù)調(diào)度和執(zhí)行的一個基...
    wxhan閱讀 622評論 0 3

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