iOS多線程詳解

一.進(jìn)程和線程

計算機(jī)的核心是CPU三痰,它承擔(dān)了所有的計算任務(wù)。它就像一座工廠窜管,時刻在運(yùn)行散劫。假定工廠的電力有限,一次只能供給一個車間使用幕帆。也就是說获搏,一個車間開工的時候,其他車間都必須停工蜓肆。背后的含義就是颜凯,單個CPU一次只能運(yùn)行一個任務(wù)谋币。進(jìn)程就好比工廠的車間,它代表CPU所能處理的單個任務(wù)症概。任一時刻蕾额,CPU總是運(yùn)行一個進(jìn)程,其他進(jìn)程處于非運(yùn)行狀態(tài)彼城。一個車間里诅蝶,可以有很多工人。他們協(xié)同完成一個任務(wù)募壕。線程就好比車間里的工人调炬。一個進(jìn)程可以包括多個線程。車間的空間是工人們共享的舱馅,比如許多房間是每個工人都可以進(jìn)出的缰泡。這象征一個進(jìn)程的內(nèi)存空間是共享的,每個線程都可以使用這些共享內(nèi)存代嗤〖可是,每間房間的大小不同干毅,有些房間最多只能容納一個人宜猜,比如廁所。里面有人的時候硝逢,其他人就不能進(jìn)去了姨拥。這代表一個線程使用某些共享內(nèi)存時,其他線程必須等它結(jié)束渠鸽,才能使用這一塊內(nèi)存叫乌。一個防止他人進(jìn)入的簡單方法,就是門口加一把鎖拱绑。先到的人鎖上門综芥,后到的人看到上鎖,就在門口排隊猎拨,等鎖打開再進(jìn)去膀藐。這就叫"互斥鎖"(Mutual exclusion,縮寫 Mutex)红省,防止多個線程同時讀寫某一塊內(nèi)存區(qū)域额各。還有些房間,可以同時容納n個人吧恃,比如廚房虾啦。也就是說,如果人數(shù)大于n,多出來的人只能在外面等著傲醉。這好比某些內(nèi)存區(qū)域蝇闭,只能供給固定數(shù)目的線程使用。這時的解決方法硬毕,就是在門口掛n把鑰匙呻引。進(jìn)去的人就取一把鑰匙,出來時再把鑰匙掛回原處吐咳。后到的人發(fā)現(xiàn)鑰匙架空了逻悠,就知道必須在門口排隊等著了。這種做法叫做"信號量"(Semaphore)韭脊,用來保證多個線程不會互相沖突童谒。
不難看出,mutex是semaphore的一種特殊情況(n=1時)沪羔。也就是說饥伊,完全可以用后者替代前者。但是蔫饰,因為mutex較為簡單撵渡,且效率高,所以在必須保證資源獨(dú)占的情況下死嗦,還是采用這種設(shè)計。
操作系統(tǒng)的設(shè)計粒氧,因此可以歸結(jié)為三點(diǎn):
(1)以多進(jìn)程形式越除,允許多個任務(wù)同時運(yùn)行;
(2)以多線程形式外盯,允許單個任務(wù)分成不同的部分運(yùn)行摘盆;
(3)提供協(xié)調(diào)機(jī)制,一方面防止進(jìn)程之間和線程之間產(chǎn)生沖突饱苟,另一方面允許進(jìn)程之間和線程之間共享資源孩擂。
摘自<<進(jìn)程與線程的一個簡單解釋-阮一峰>>

二.多線程

1.優(yōu)點(diǎn)
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
2.缺點(diǎn)
創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、椫吭空間(子線程512KB、主線程1MB蟆沫,也可以使用-setStackSize:設(shè)置寒矿,但必須是4K的倍數(shù),而且最小是16K)肄渗,創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間镇眷,如果開啟大量的線程,會降低程序的性能翎嫡,線程越多欠动,CPU在調(diào)度線程上的開銷就越大。
程序設(shè)計更加復(fù)雜:比如線程之間的通信惑申、多線程的數(shù)據(jù)共享等問題具伍。

三.四種方案對比

圖片來自http://www.cocoachina.com/ios/20170707/19769.html

1.pthread

 - (IBAction)create_pthread:(id)sender {
    //聲名線程變量
    pthread_t thread;
    /*
     pthread_create方法介紹:
     參數(shù)一:線程對象的地址
     參數(shù)二:設(shè)定線程的屬性(例如:子線程占用多少棧空間:512KB圈驼。人芽。。一般設(shè)置為空NULL)
     參數(shù)三:子線程執(zhí)行任務(wù)的函數(shù)聲明
     參數(shù)四:傳遞給函數(shù)的參數(shù)
     返回值:0代表線程創(chuàng)建成功
     */
    int result = pthread_create(&thread, NULL, run,NULL);//創(chuàng)建線程
    if (!result) {
        NSLog(@"線程創(chuàng)建成功");
    }
}
//調(diào)用函數(shù)
void *run(void *param) {
    for (NSInteger i = 0; i < 10000; i ++) {
        NSLog(@"線程:%@正在打印++++++",[NSThread currentThread]);
    }
    return NULL;
}

2.NSThread

  • 方法一
- (IBAction)createThreadMethod1:(id)sender {
    //創(chuàng)建線程對象
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"執(zhí)行任務(wù)1"];
    
    //設(shè)置名稱
    thread1.name = @"子線程1";
    //設(shè)置線程優(yōu)先級
    [thread1 setThreadPriority:0.8];
    //開啟任務(wù)
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"執(zhí)行任務(wù)2"];
    thread2.name = @"子線程2";
    [thread2 setThreadPriority:1.0];
    [thread2 start];
}
- (void)run:(NSString *)param {
    for (NSInteger i = 0; i < 100; i++) {
        NSLog(@"%@-%@",[NSThread currentThread],param);
    }
}
  • 方法二
- (IBAction)createThreadMethod2:(id)sender {
    //默認(rèn)開始子線程
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"子線程3"];
}
  • 方法三
- (IBAction)createThreadMethod3:(id)sender {
    //默認(rèn)開啟子線程
    [self performSelectorInBackground:@selector(run:) withObject:@"子線程4"];
}

3.GCD

(1)簡介

全稱Grand Central Dispatch绩脆,它是蘋果為多核的并行運(yùn)算提出的解決方案萤厅,會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核)靴迫,最重要的是會自動管理線程的生命周期(創(chuàng)建線程惕味、調(diào)度任務(wù)、銷毀線程)玉锌,只需要告訴GCD要執(zhí)行什么任務(wù),不需要編寫任何管理代碼名挥。同時它使用的也是 c語言,不過由于使用了 Block主守,使得使用起來更加方便.

(2)基本概念

任務(wù)(block):任務(wù)就是將要在線程中執(zhí)行的代碼躺同,將這段代碼用block封裝好,然后將這個任務(wù)添加到指定的執(zhí)行方式(同步執(zhí)行和異步執(zhí)行)丸逸,等待CPU從隊列中取出任務(wù)放到對應(yīng)的線程中執(zhí)行。
同步(sync):前一個任務(wù)沒有執(zhí)行完畢剃袍,后面的任務(wù)不能執(zhí)行黄刚,不開子線程。
異步(async):前一個任務(wù)沒有執(zhí)行完畢民效,后面的任務(wù)可以執(zhí)行憔维。
dispatch_sync: dispatch_sync則是同步扔一個block到queue中,即扔了我就等著畏邢,等到queue排隊把這個block執(zhí)行完了之后业扒,才繼續(xù)執(zhí)行下一行代碼。
dispatch_async:異步扔一個block到queue中舒萎,即扔完我就不管了程储,繼續(xù)執(zhí)行我的下一行代碼。實際上當(dāng)下一行代碼執(zhí)行時,這個block還未執(zhí)行章鲤,只是入了隊列queue摊灭,queue會排隊來執(zhí)行這個block。
隊列:裝載線程任務(wù)的隊形結(jié)構(gòu)败徊。(系統(tǒng)以先進(jìn)先出的方式調(diào)度隊列中的任務(wù)執(zhí)行)帚呼。在GCD中有兩種隊列:串行隊列和并發(fā)隊列。
并發(fā)隊列:線程可以同時一起進(jìn)行執(zhí)行皱蹦。單核CPU的情況下煤杀,是系統(tǒng)在多條線程之間快速的切換。(并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效)
串行隊列:線程只能依次有序的執(zhí)行沪哺。
主隊列:GCD自帶的一種特殊的串行隊列沈自,放在主隊列中的任務(wù),都會放到主線程中執(zhí)行凤粗。
全局隊列:GCD自帶的一種特殊的并行隊列.
GCD總結(jié):將任務(wù)(要在線程中執(zhí)行的操作block)添加到隊列(自己創(chuàng)建或使用全局并發(fā)隊列)酥泛,并且指定執(zhí)行任務(wù)的方式(異步dispatch_async,同步dispatch_sync)

(3)創(chuàng)建
    //創(chuàng)建一個串行隊列【參數(shù)一:隊列的唯一標(biāo)識符嫌拣,用于DEBUG柔袁,可為空。參數(shù)二:串行還是并行標(biāo)識】
    dispatch_queue_t serialQueue = dispatch_queue_create("A Serial Queue", DISPATCH_QUEUE_SERIAL);
    //創(chuàng)建一個并行隊列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("A Concurrent Queue", DISPATCH_QUEUE_CONCURRENT);
    //獲取主隊列
    dispatch_queue_t systemSerialQueue = dispatch_get_main_queue();
    //獲取全局隊列【參數(shù)一:隊列優(yōu)先級 參數(shù)二:默認(rèn)傳0即可】
    dispatch_queue_t systemConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
(4)八種情況
  • 異步并行

隊列中的任務(wù)并發(fā)執(zhí)行,會創(chuàng)建一個或多個子線程异逐。如果隊列中的任務(wù)沒有執(zhí)行完畢捶索,系統(tǒng)則會執(zhí)行其他任務(wù)。

- (IBAction)concurrentQueueAsync:(id)sender {
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完畢",[NSThread currentThread]);
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}

控制臺輸出:
<NSThread: 0x1c4072080>{number = 1, name = main}:打印+完畢
<NSThread: 0x1c4072080>{number = 1, name = main}:打印-完畢
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
  • 異步串行

隊列中的任務(wù)依次執(zhí)行,會創(chuàng)建一個或多個子線程灰瞻。如果隊列中的任務(wù)沒有執(zhí)行完畢腥例,系統(tǒng)則會執(zhí)行其他任務(wù)。

- (IBAction)serialQueueAsync:(id)sender {
    dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完畢",[NSThread currentThread]);
    
    dispatch_async(serialQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x1c0263300>{number = 1, name = main}:打印+完畢
<NSThread: 0x1c0263300>{number = 1, name = main}:打印-完畢
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
  • 同步并行

隊列中的任務(wù)依次執(zhí)行酝润,不會創(chuàng)建子線程燎竖。只有隊列中的任務(wù)執(zhí)行完畢,系統(tǒng)才會執(zhí)行下一個任務(wù)要销,會阻塞當(dāng)前線程构回。

- (IBAction)concurrentQueueSync:(id)sender {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(concurrentQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完畢",[NSThread currentThread]);
    
    dispatch_sync(concurrentQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:打印+完畢
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:打印-完畢
  • 同步串行

隊列中的任務(wù)依次執(zhí)行,不會創(chuàng)建子線程疏咐。只有隊列中的任務(wù)執(zhí)行完畢纤掸,系統(tǒng)才會執(zhí)行下一個任務(wù),會阻塞當(dāng)前線程。

- (IBAction)serialQueueSync:(id)sender {
    dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完畢",[NSThread currentThread]);
    
    dispatch_sync(serialQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:打印+完畢
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:打印-完畢
  • 主隊列同步執(zhí)行

主隊列同步執(zhí)行會造成死鎖 原因在后面

    dispatch_sync(dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
  • 主隊列異步執(zhí)行

隊列中的任務(wù)依次在主線程中執(zhí)行浑塞,不會創(chuàng)建子線程借跪。隊列中的任務(wù)沒有執(zhí)行完畢,系統(tǒng)則會執(zhí)行下一個任務(wù),不會阻塞當(dāng)前線程酌壕。

- (IBAction)mainQueueAsync:(id)sender {
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完畢",[NSThread currentThread]);
    
    dispatch_async(mainQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x60000007b340>{number = 1, name = main}:打印+完畢
<NSThread: 0x60000007b340>{number = 1, name = main}:打印-完畢
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
  • 全局隊列異步執(zhí)行

隊列中的任務(wù)并發(fā)執(zhí)行掏愁,會創(chuàng)建一個或多個子線程歇由。隊列中的任務(wù)沒有執(zhí)行完畢,系統(tǒng)則會執(zhí)行下一個任務(wù)托猩,不會阻塞當(dāng)前線程印蓖。

- (IBAction)globalQueueAsync:(id)sender {
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_async(globalQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完畢",[NSThread currentThread]);
    
    dispatch_async(globalQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x60400007b7c0>{number = 1, name = main}:打印+完畢
<NSThread: 0x60400007b7c0>{number = 1, name = main}:打印-完畢
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
  • 全局隊列同步執(zhí)行

隊列中的任務(wù)依次執(zhí)行,不會創(chuàng)建子線程京腥。隊列中的任務(wù)沒有執(zhí)行完畢赦肃,系統(tǒng)不會執(zhí)行下一個任務(wù),會阻塞當(dāng)前線程公浪。

- (IBAction)globalQueueSync:(id)sender {
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_sync(globalQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完畢",[NSThread currentThread]);
    
    dispatch_sync(globalQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:打印+完畢
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:打印-完畢
(5)主隊列同步執(zhí)行造成死鎖的原因
dispatch_sync(dispatch_get_main_queue(), ^{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"%@:++++++++++",[NSThread currentThread]);
    }
});
同步:只有當(dāng)前任務(wù)執(zhí)行完畢他宛,系統(tǒng)才會執(zhí)行下一個任務(wù)。
主隊列:Block中的任務(wù)和dispatch_sync函數(shù)都是需要在主隊列中執(zhí)行的欠气。
Block中的任務(wù)需要主線程執(zhí)行完dispatch_sync函數(shù)才會執(zhí)行厅各,dispatch_sync函數(shù)需要block執(zhí)行完畢才會執(zhí)行其他任務(wù),兩者相互等待预柒,造成死鎖队塘。
  • 非主隊列不會造成死鎖的原因

dispatch_sync是在主隊列com.apple.main-thread中執(zhí)行的。
Block中的任務(wù)是在創(chuàng)建的serial.queue隊列中執(zhí)行的宜鸯。
兩者并不是在同一個隊列中執(zhí)行憔古。Block的執(zhí)行不需要等待dispatch_sync函數(shù)執(zhí)行完畢才去執(zhí)行。

- (IBAction)serialQueueSync:(id)sender {
    dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"當(dāng)前隊列:%@-%@:++++++++++",dispatch_get_current_queue(),[NSThread currentThread]);
    });
    NSLog(@"當(dāng)前隊列:%@-%@:打印完畢",dispatch_get_current_queue(),[NSThread currentThread]);
}
控制臺輸出:
當(dāng)前隊列:<OS_dispatch_queue: serial.queue>-<NSThread: 0x6000000790c0>{number = 1, name = main}:++++++++++
當(dāng)前隊列:<OS_dispatch_queue_main: com.apple.main-thread>-<NSThread: 0x6000000790c0>{number = 1, name = main}:打印完畢
  • 在同一個隊列同步執(zhí)行兩個任務(wù)是造成死鎖的原因

在程序執(zhí)行到第二個星號位置的dispatch_sync時淋袖,當(dāng)前隊列serial.queue需要執(zhí)行dispatch_sync函數(shù)鸿市,但是dispatch_sync函數(shù)需要等待Block執(zhí)行完畢才會執(zhí)行,因為serial.queue是串行隊列即碗,其中的任務(wù)是一個一個執(zhí)行的焰情。Block任務(wù)排列在第二個星號位置的dispatch_sync函數(shù)的后面,Block中任務(wù)的執(zhí)行需要等待dispatch_sync函數(shù)的執(zhí)行才會執(zhí)行,即造成了兩者互相等待的情況剥懒,所以造成死鎖内舟。

- (IBAction)serialQueueSync:(id)sender {
    dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"打印+++++:%@-%@",dispatch_get_current_queue(),[NSThread currentThread]);
        *dispatch_sync(serialQueue, ^{
            NSLog(@"打印------:%@-%@",dispatch_get_current_queue(),[NSThread currentThread]);
        });
    });
}
(5)GCD線程通訊

當(dāng)我們完成了耗時操作,需要回到主線程進(jìn)行UI更新初橘,這時就需要用到線程通訊:

dispatch_async(dispatch_queue_create("A Concurrent Queue", DISPATCH_QUEUE_CONCURRENT), ^{
    //耗時操作
    dispatch_async(dispatch_get_main_queue(), ^{
        //更新UI
    });
});

(6)GCD柵欄函數(shù)

  • 什么是柵欄函數(shù)
    1.GCD柵欄函數(shù)即dispatch_barrier_async函數(shù),它等待所有位于barrier函數(shù)之前的操作執(zhí)行完畢后執(zhí)行,并且在barrier函數(shù)執(zhí)行之后,barrier函數(shù)之后的操作才會得到執(zhí)行验游。
    2.當(dāng)任務(wù)需要異步進(jìn)行,但是這些任務(wù)需要分成兩組來執(zhí)行壁却,第一組完成之后才能進(jìn)行第二組的操作。這時候就用了到GCD的柵欄函數(shù)dispatch_barrier_async裸准。
  • 柵欄函數(shù)的使用
- (IBAction)barrierMethod:(id)sender {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("a concurrent queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)1:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)2:%@",[NSThread currentThread]);
    });
    dispatch_barrier_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"柵欄任務(wù):%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)3:%@",[NSThread currentThread]);
    });
    
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)4:%@",[NSThread currentThread]);
    });
}
控制臺輸出:
任務(wù)2:<NSThread: 0x604000275bc0>{number = 4, name = (null)}
任務(wù)1:<NSThread: 0x600000274d40>{number = 3, name = (null)}
柵欄任務(wù):<NSThread: 0x604000275bc0>{number = 4, name = (null)}
任務(wù)4:<NSThread: 0x600000274d40>{number = 3, name = (null)}
任務(wù)3:<NSThread: 0x604000275bc0>{number = 4, name = (null)}
上面的柵欄函數(shù)使用
dispatch_barrier_sync(concurrentQueue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"柵欄任務(wù):%@",[NSThread currentThread]);
});
控制臺輸出為:
 任務(wù)2:<NSThread: 0x604000277180>{number = 3, name = (null)}
 任務(wù)1:<NSThread: 0x604000278a40>{number = 4, name = (null)}
 柵欄任務(wù):<NSThread: 0x60400006b600>{number = 1, name = main}
 任務(wù)3:<NSThread: 0x604000277180>{number = 3, name = (null)}
 任務(wù)4:<NSThread: 0x60000046bb80>{number = 5, name = (null)}
在柵欄函數(shù)的block任務(wù)執(zhí)行完畢以前展东,主線程都是阻塞狀態(tài)。

(7)GCD延遲函數(shù)

  • 秒的單位換算
1秒=1000毫秒
1毫秒=1000微秒
1微秒=1000納秒
1納秒=1000皮秒
1皮秒=1000飛秒
  • 系統(tǒng)宏定義
#define NSEC_PER_SEC 1000000000ull //10億納秒等于1秒
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
#define DISPATCH_TIME_NOW (0ull)//代表從當(dāng)前立即開始的時間
  • 延遲一段代碼3秒以后執(zhí)行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"在3秒鐘以后輸出這段文字");
});

(8)GCD一次性任務(wù)【可用于設(shè)計單例】

- (IBAction)onceTokenTask:(id)sender {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"在程序運(yùn)行中炒俱,這段代碼只會被執(zhí)行一次");
    });
}

(8)dispatch_apply函數(shù)的使用

  • 函數(shù)作用
    把一項任務(wù)提交到隊列中多次執(zhí)行盐肃,具體是串行執(zhí)行還是并行執(zhí)行由隊列本身決定,dispatch_apply不會立即返回爪膊,在執(zhí)行完畢后才會返回,是同步的調(diào)用砸王。
- (IBAction)gcdApply:(id)sender {
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("aConcurrentQueue", DISPATCH_QUEUE_SERIAL);
    size_t size = 5;
    /*
     第一個參數(shù):任務(wù)在隊列中執(zhí)行的次數(shù)
     第二個參數(shù):任務(wù)執(zhí)行的隊列
     Block參數(shù)代表當(dāng)前任務(wù)在隊列中是第幾次執(zhí)行
     */
    dispatch_apply(size, concurrentQueue, ^(size_t index) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"當(dāng)前打印:%zu:線程:%@",index,[NSThread currentThread]);
    });
    NSLog(@"執(zhí)行其他任務(wù):%@",[NSThread currentThread]);
}
控制臺輸出:
當(dāng)前打印:0:線程:<NSThread: 0x604000261940>{number = 1, name = main}
當(dāng)前打印:2:線程:<NSThread: 0x6000004696c0>{number = 3, name = (null)}
當(dāng)前打印:3:線程:<NSThread: 0x60000027b2c0>{number = 4, name = (null)}
當(dāng)前打印:1:線程:<NSThread: 0x6040006623c0>{number = 5, name = (null)}
當(dāng)前打印:4:線程:<NSThread: 0x604000261940>{number = 1, name = main}
執(zhí)行其他任務(wù):<NSThread: 0x604000261940>{number = 1, name = main}

(8)隊列組

  • 業(yè)務(wù)場景示例
    當(dāng)我們在開發(fā)一個音樂APP時推盛,需要下載很多首音樂,當(dāng)下載完成以后需要通知用戶所有音樂下載完成谦铃,這時我們就可以使用隊列組實現(xiàn)這一場景耘成。
  • 方法介紹
//dispatch_group_create()用戶創(chuàng)建一個隊列組
dispatch_group_t group = dispatch_group_create();
//用于添加對應(yīng)隊列組中的未執(zhí)行完畢的任務(wù)數(shù),執(zhí)行一次驹闰,未執(zhí)行完畢的任務(wù)數(shù)加1瘪菌,當(dāng)未執(zhí)行完畢任務(wù)數(shù)為0的時候,才會使dispatch_group_wait解除阻塞和dispatch_group_notify的block執(zhí)行
dispatch_group_enter(group);
//用于減少隊列組中的未執(zhí)行完畢的任務(wù)數(shù)嘹朗,執(zhí)行一次师妙,未執(zhí)行完畢的任務(wù)數(shù)減1,dispatch_group_enter和dispatch_group_leave要匹配屹培,即有加就要有減,不然系統(tǒng)會認(rèn)為group任務(wù)沒有執(zhí)行完畢
dispatch_group_leave(group);
//延時執(zhí)行當(dāng)前任務(wù)默穴,會阻塞當(dāng)前線程
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC));
//在指定的隊列組和隊列中異步執(zhí)行任務(wù)
dispatch_group_async(group, queue, ^{
});
//當(dāng)隊列組中所有任務(wù)執(zhí)行完畢以后執(zhí)行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
  • 使用示例
- (IBAction)queueGroup:(id)sender {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"%@任務(wù)一",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:4.0f];
        NSLog(@"%@任務(wù)二",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任務(wù)全部完成了");
    });
    NSLog(@"額外任務(wù):%@",[NSThread currentThread]);
}
控制臺輸出:
額外任務(wù):<NSThread: 0x600000065000>{number = 1, name = main}
<NSThread: 0x60000026f4c0>{number = 3, name = (null)}任務(wù)一
<NSThread: 0x600000460440>{number = 4, name = (null)}任務(wù)二
任務(wù)全部完成了
  • 不能正確通知隊列組中所有任務(wù)已經(jīng)完成的情況
- (IBAction)queueGroup:(id)sender {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"%@任務(wù)一",[NSThread currentThread]);
        dispatch_async(queue, ^{
            [NSThread sleepForTimeInterval:8.0f];
            NSLog(@"%@任務(wù)三",[NSThread currentThread]);
        });
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:4.0f];
        NSLog(@"%@任務(wù)二",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任務(wù)全部完成了");
    });
    NSLog(@"額外任務(wù):%@",[NSThread currentThread]);
}
控制臺輸出:
額外任務(wù):<NSThread: 0x600000079440>{number = 1, name = main}
<NSThread: 0x60000046f7c0>{number = 3, name = (null)}任務(wù)一
<NSThread: 0x600000470ac0>{number = 4, name = (null)}任務(wù)二
任務(wù)全部完成了
<NSThread: 0x60000046f7c0>{number = 3, name = (null)}任務(wù)三

我們發(fā)現(xiàn),在dispatch_group_notify函數(shù)調(diào)用以后褪秀,任務(wù)三才執(zhí)行蓄诽。很明顯,這里并沒有實現(xiàn)在所有任務(wù)執(zhí)行完畢以后才去調(diào)用dispatch_group_notify函數(shù)溜歪。解決方案如下:

我們需要使用dispatch_group_enter和dispatch_group_leave函數(shù)
- (IBAction)queueGroup:(id)sender {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue, ^{
        //在每一次開始執(zhí)行任務(wù)以前進(jìn)行計數(shù)加
        dispatch_group_enter(group);
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"%@任務(wù)一",[NSThread currentThread]);
        dispatch_async(queue, ^{
            [NSThread sleepForTimeInterval:8.0f];
            NSLog(@"%@任務(wù)三",[NSThread currentThread]);
            //在每一次任務(wù)執(zhí)行完畢以后進(jìn)行計數(shù)減
            dispatch_group_leave(group);
        });
    });
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        [NSThread sleepForTimeInterval:4.0f];
        NSLog(@"%@任務(wù)二",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任務(wù)全部完成了");
    });
    NSLog(@"額外任務(wù):%@",[NSThread currentThread]);
}
控制臺輸出如下:
額外任務(wù):<NSThread: 0x600000073ec0>{number = 1, name = main}
<NSThread: 0x6000002792c0>{number = 3, name = (null)}任務(wù)一
<NSThread: 0x60000027a000>{number = 4, name = (null)}任務(wù)二
<NSThread: 0x6000002792c0>{number = 3, name = (null)}任務(wù)三
任務(wù)全部完成了

(9)GCD定時器

  • Dispatch Source
    Dispatch Source是GCD中的一種基本數(shù)據(jù)類型若专,從字面意思可稱其為調(diào)度源,它用于處理特定的系統(tǒng)底層事件蝴猪,即:當(dāng)一些特定的系統(tǒng)底層事件發(fā)生時调衰,調(diào)度源會捕捉到這些事件,然后可以做相應(yīng)的邏輯處理自阱。
  • Dispatch Source可處理的事件類型
    DISPATCH_SOURCE_TYPE_DATA_ADD 自定義的事件嚎莉,變量增加
    DISPATCH_SOURCE_TYPE_DATA_OR 自定義的事件,變量OR
    DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口發(fā)送
    DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
    DISPATCH_SOURCE_TYPE_PROC 進(jìn)程監(jiān)聽,如進(jìn)程的退出沛豌、創(chuàng)建一個或更多的子線程趋箩、進(jìn)程收到UNIX信號
    DISPATCH_SOURCE_TYPE_READ IO操作,如對文件的操作加派、socket操作的讀響應(yīng)
    DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信號時響應(yīng)
    DISPATCH_SOURCE_TYPE_TIMER 定時器
    DISPATCH_SOURCE_TYPE_VNODE 文件狀態(tài)監(jiān)聽叫确,文件被刪除、移動芍锦、重命名
    DISPATCH_SOURCE_TYPE_WRITE IO操作竹勉,如對文件的操作、socket操作的寫響應(yīng)
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //創(chuàng)建 Timer Source
    dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 0);
    //設(shè)置 Timer Source 的啟動時間娄琉、間隔時間次乓、精度
    dispatch_source_set_timer(timerSource, time, 1.0*NSEC_PER_SEC, 0);
    //設(shè)置時間回調(diào)
    dispatch_source_set_event_handler(timerSource, ^{
        NSLog(@"當(dāng)前時間:%@",[NSDate date]);
    });
    //開始執(zhí)行 Dispatch Source
    dispatch_resume(timerSource);
    //強(qiáng)引用避免被提前釋放
    self.timerSource = timerSource;

4.NSOperation

NSOperation是GCD的封裝吓歇,完全面向?qū)ο蟾躺摺SOperation是抽象類逃呼,需要使用它的子類NSInvocationOperation 和 NSBlockOperation纱昧,或者自定義子類也行搞坝。

(1)NSInvocationOperation的使用

  • 同步執(zhí)行任務(wù)【會阻塞當(dāng)前線程】
- (IBAction)syncTask:(id)sender {
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printTask) object:nil];
    [invocationOperation start];
    NSLog(@"%@:其他任務(wù)",[NSThread currentThread]);
}

- (void)printTask {
    [NSThread sleepForTimeInterval:2.0f];
    NSLog(@"%@-%@:執(zhí)行打印任務(wù)",dispatch_get_current_queue(),[NSThread currentThread]);
}

控制臺輸出:
<OS_dispatch_queue_main: com.apple.main-thread>-<NSThread: 0x6000002638c0>{number = 1, name = main}:執(zhí)行打印任務(wù)
<NSThread: 0x6000002638c0>{number = 1, name = main}:其他任務(wù)
  • 異步執(zhí)行任務(wù)【不會阻塞當(dāng)前線程】
- (IBAction)asyncTask:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printTask) object:nil];
    [queue addOperation:operation];
    NSLog(@"%@:其他任務(wù)",[NSThread currentThread]);
}
- (void)printTask {
    [NSThread sleepForTimeInterval:2.0f];
    NSLog(@"%@-%@:執(zhí)行打印任務(wù)",dispatch_get_current_queue(),[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x60400006e440>{number = 1, name = main}:其他任務(wù)
<OS_dispatch_queue: NSOperationQueue 0x60000022d300 (QOS: UNSPECIFIED)>-<NSThread: 0x60400046d180>{number = 3, name = (null)}:執(zhí)行打印任務(wù)

(2)NSBlockOperation的使用

  • 同步執(zhí)行任務(wù) 并發(fā) 會阻塞當(dāng)前線程
- (IBAction)nsblockOperationSync:(id)sender {
    NSBlockOperation *operation = [[NSBlockOperation alloc] init];
    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)三:%@",[NSThread currentThread]);
    }];
    [operation start];
    NSLog(@"%@其他任務(wù)",[NSThread currentThread]);
}
控制臺輸出:
任務(wù)三:<NSThread: 0x60000027d380>{number = 4, name = (null)}
任務(wù)一:<NSThread: 0x60400006fcc0>{number = 1, name = main}
任務(wù)二:<NSThread: 0x6000002778c0>{number = 3, name = (null)}
<NSThread: 0x60400006fcc0>{number = 1, name = main}其他任務(wù)
  • 異步執(zhí)行任務(wù) 非主隊列則為并發(fā)隊列 不會阻塞當(dāng)前線程 后添加的operation先執(zhí)行
- (IBAction)nsblockOperationAsync:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
    }];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    NSLog(@"%@其他任務(wù)",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x604000065f40>{number = 1, name = main}其他任務(wù)
任務(wù)二:<NSThread: 0x600000279540>{number = 3, name = (null)}
任務(wù)一:<NSThread: 0x604000263800>{number = 4, name = (null)}

(3)NSOperation操作依賴

  • 沒有設(shè)置依賴關(guān)系
- (IBAction)setttingDependency:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.0f];
        NSLog(@"任務(wù)三:%@",[NSThread currentThread]);
    }];
    [queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
    NSLog(@"%@其他任務(wù)",[NSThread currentThread]);
}
控制臺輸出如下:
<NSThread: 0x600000074d40>{number = 1, name = main}其他任務(wù)
任務(wù)三:<NSThread: 0x604000470340>{number = 3, name = (null)}
任務(wù)二:<NSThread: 0x60000046e980>{number = 4, name = (null)}
任務(wù)一:<NSThread: 0x60000046a2c0>{number = 5, name = (null)}
  • 設(shè)置了依賴關(guān)系
- (IBAction)setttingDependency:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.0f];
        NSLog(@"任務(wù)三:%@",[NSThread currentThread]);
    }];
    [operation2 addDependency:operation1];
    [operation3 addDependency:operation2];
    [queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
    NSLog(@"%@其他任務(wù)",[NSThread currentThread]);
}
控制臺輸出如下:
<NSThread: 0x600000261d00>{number = 1, name = main}其他任務(wù)
任務(wù)一:<NSThread: 0x60000047a380>{number = 3, name = (null)}
任務(wù)二:<NSThread: 0x60400027e680>{number = 4, name = (null)}
任務(wù)三:<NSThread: 0x60400027e680>{number = 4, name = (null)}

(4)NSOperation設(shè)置最大并發(fā)數(shù)

- (IBAction)settingMaxCount:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 2;
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任務(wù)三:%@",[NSThread currentThread]);
    }];
    [queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
}
控制臺輸出:
12:03:51 任務(wù)二:<NSThread: 0x600000261680>{number = 3, name = (null)}
12:03:51 任務(wù)一:<NSThread: 0x600000261700>{number = 4, name = (null)}
12:03:53 任務(wù)三:<NSThread: 0x604000474040>{number = 5, name = (null)}

(4)線程通信

- (IBAction)threadCommunication:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"耗時任務(wù):%@",[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"%@回主線程更新UI",[NSThread currentThread]);
        }];
    }];
    [queue addOperation:operation];
}
控制臺輸出:
耗時任務(wù):<NSThread: 0x60000027e140>{number = 3, name = (null)}
<NSThread: 0x60400006b640>{number = 1, name = main}回主線程更新UI

四.線程安全

  • 線程不安全示例 賣票程序
    當(dāng)我們運(yùn)行下列程序:
- (IBAction)simultaneousAccess:(id)sender {
    //賣出了多少張票 初始為0張
    self.soldedTicketsCount = 0;
    //剩余多少票 初始為10000張
    self.totalTicketsCount = 10000;
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_threadUnsafety) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_threadUnsafety) object:nil];
    secondThread.name = @"窗口二";
    [firstThread start];
    [secondThread start];
}
- (void)sellTickets_threadUnsafety {
    while (1) {
        if (self.totalTicketsCount > 0) {
            self.totalTicketsCount --;
            self.soldedTicketsCount ++;
            NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
        } else {
            NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
            return;
        }
    }
}
控制臺輸出:
票賣完了:剩余:0賣出了:9999
票賣完了:剩余:0賣出了:9999

可以明顯看到坝咐,最后的輸出結(jié)果是不正確的留储,因為在多個線程同時執(zhí)行一個任務(wù)的時候卜录,就會產(chǎn)生線程安全問題鹅士。ios中解決線程安全問題的辦法有很多逃默,下面介紹四種:@synchronized鹃愤、NSLock、pthread_mutex_t完域、信號量软吐。

(1).@synchronized

  • 使用@synchronized加鎖 可以保證同一時刻只有一個線程訪問特定程序
  • @synchronized(nil)無效
  • @synchronized只對同一個對象有效
- (IBAction)synchronizedMethod:(id)sender {
    self.soldedTicketsCount = 0;
    self.totalTicketsCount = 10000;
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_synchronized) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_synchronized) object:nil];
    secondThread.name = @"窗口二";
    [firstThread start];
    [secondThread start];
}
- (void)sellTickets_synchronized {
    @synchronized(self) {
        while (1) {
            if (self.totalTicketsCount > 0) {
                self.totalTicketsCount --;
                self.soldedTicketsCount ++;
                NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
            } else {
                NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
                return;
            }
        }
    }
}
控制臺最終輸出:
 票賣完了:剩余:0賣出了:10000
 票賣完了:剩余:0賣出了:10000

(2).NSLock

  • NSLock 遵循 NSLocking 協(xié)議,lock 方法是加鎖吟税,unlock 是解鎖凹耙,tryLock 是嘗試加鎖,如果失敗的話返回 NO肠仪,lockBeforeDate: 是在指定Date之前嘗試加鎖肖抱,如果在指定時間之前都不能加鎖,則返回NO异旧。
- (IBAction)nslockMethod:(id)sender {
    self.soldedTicketsCount = 0;
    self.totalTicketsCount = 10000;
    //NSLock對象
    self.lock = [[NSLock alloc] init];
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_nslock) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_nslock) object:nil];
    secondThread.name = @"窗口二";
    [firstThread start];
    [secondThread start];
}
- (void)sellTickets_nslock {
    while (1) {
        //加鎖
        [self.lock lock];
        if (self.totalTicketsCount > 0) {
            self.totalTicketsCount --;
            self.soldedTicketsCount ++;
            NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
            //解鎖
            [self.lock unlock];
        } else {
            NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
            //解鎖
            [self.lock unlock];
            return;
        }
    }
}
控制臺輸出:
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000

(3).pthread_mutex_t

  • pthread_mutex_t是基于C語言的意述。
  • pthread_mutex_init用于創(chuàng)建并初始化pthread_mutex_t。
  • pthread_mutex_unlock解鎖
  • pthread_mutex_lock加鎖
//聲名一個靜態(tài)變量
static pthread_mutex_t mutex;
- (IBAction)pthread_mutex_tMethod:(id)sender {
    self.soldedTicketsCount = 0;
    self.totalTicketsCount = 100000;
    //創(chuàng)建并初始化
    pthread_mutex_init(&mutex, NULL);
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
    secondThread.name = @"窗口二";
    NSThread *thirdThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
    thirdThread.name = @"窗口三";
    NSThread *fourthThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
    fourthThread.name = @"窗口四";
    [firstThread start];
    [secondThread start];
    [thirdThread start];
    [fourthThread start];
}
- (void)sellTickets_pthread_mutex_t {
    while (1) {
        //加鎖
        pthread_mutex_lock(&mutex);
        if (self.totalTicketsCount > 0) {
            self.totalTicketsCount --;
            self.soldedTicketsCount ++;
            NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
            //解鎖
            pthread_mutex_unlock(&mutex);
        } else {
            NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
            //解鎖
            pthread_mutex_unlock(&mutex);
            return;
        }
    }
}
控制臺輸出:
票賣完了:剩余:0賣出了:100000
票賣完了:剩余:0賣出了:100000
票賣完了:剩余:0賣出了:100000
票賣完了:剩余:0賣出了:100000

(4).semaphore信號量

  • 首先要注意吮蛹,信號量和信號是完全兩碼事荤崇。信號量是一個計數(shù)器,常用于處理進(jìn)程或線程的同步問題潮针,特別是對臨界資源訪問的同步术荤。臨界資源可以簡單地理解為在某一時刻只能由一個進(jìn)程或線程進(jìn)行操作的資源。通常每篷,程序?qū)蚕碣Y源的訪問的代碼只是很短的一段瓣戚,但就是這一段代碼引發(fā)了進(jìn)程之間的競態(tài)條件。這段代碼稱為關(guān)鍵代碼段焦读,或者臨界區(qū)子库。對進(jìn)程同步,也就是確保任一時刻只有一個進(jìn)程能進(jìn)入關(guān)鍵代碼段矗晃。
  • 信號量是一個資源計數(shù)器仑嗅,對信號量有兩個操作來達(dá)到互斥,分別是P和V操作。 一般情況是這樣進(jìn)行臨界訪問或互斥訪問的: 設(shè)信號量值為1无畔, 當(dāng)一個進(jìn)程A運(yùn)行時,使用資源吠冤,進(jìn)行P操作浑彰,即對信號量值減1,也就是資源數(shù)少了1個拯辙。這時信號量值為0郭变。系統(tǒng)中規(guī)定當(dāng)信號量值為0是,必須等待涯保,直到信號量值不為零才能繼續(xù)操作诉濒。 這時如果進(jìn)程B想要運(yùn)行,那么也必須進(jìn)行P操作夕春,但是此時信號量為0未荒,所以無法減1,即不能P操作及志,也就阻塞片排。這樣就達(dá)到了進(jìn)程A的排他訪問。 當(dāng)進(jìn)程A運(yùn)行結(jié)束后速侈,釋放資源率寡,進(jìn)行V操作。資源數(shù)重新加1倚搬,這時信號量的值變?yōu)?. 這時進(jìn)程B發(fā)現(xiàn)資源數(shù)不為0冶共,信號量能進(jìn)行P操作了,立即執(zhí)行P操作每界。信號量值又變?yōu)?.此時進(jìn)程B占用資源捅僵,系統(tǒng)禁止其他進(jìn)程訪問資源。 這就是信號量來控制互斥的原理
  • 方法介紹
//創(chuàng)建一個信號量并制定其信號值
dispatch_semaphore_create(long value);
/*
1.如果信號值大于0 則向下執(zhí)行任務(wù)
2.如果信號值等于0 則在timeout以前 當(dāng)前進(jìn)程一直處于阻塞狀態(tài) 線程不可以訪問
*/
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//信號量加1 喚醒處于wait狀態(tài)的進(jìn)程 線程可以訪問
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
  • semaphore使用示例
- (IBAction)semaphoreMethod:(id)sender {
    self.soldedTicketsCount = 0;
    self.totalTicketsCount = 10000;
    //創(chuàng)建一個信號量 信號值設(shè)置為1
    self.semaphore = dispatch_semaphore_create(1);
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
    secondThread.name = @"窗口二";
    NSThread *thirdThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
    thirdThread.name = @"窗口三";
    NSThread *fourthThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
    fourthThread.name = @"窗口四";
    [firstThread start];
    [secondThread start];
    [thirdThread start];
    [fourthThread start];
}
- (void)sellTickets_semaphore {
    while (1) {
        //當(dāng)前信號值為1 向下執(zhí)行任務(wù)并且信號值-1
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        if (self.totalTicketsCount > 0) {
            self.totalTicketsCount --;
            self.soldedTicketsCount ++;
            NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
            //任務(wù)執(zhí)行完畢 信號值為0需要加1 解除當(dāng)前進(jìn)程的阻塞狀態(tài) 以便其他線程的訪問
            dispatch_semaphore_signal(self.semaphore);
        } else {
            NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
            //任務(wù)執(zhí)行完畢 信號值為0需要加1 解除當(dāng)前進(jìn)程的阻塞狀態(tài) 以便其他線程的訪問
            dispatch_semaphore_signal(self.semaphore);
            return;
        }
    }
}
控制臺輸出:
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盆犁,一起剝皮案震驚了整個濱河市命咐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谐岁,老刑警劉巖醋奠,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伊佃,居然都是意外死亡窜司,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門航揉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塞祈,“玉大人,你說我怎么就攤上這事帅涂∫樾剑” “怎么了尤蛮?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斯议。 經(jīng)常有香客問我产捞,道長,這世上最難降的妖魔是什么哼御? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任坯临,我火速辦了婚禮,結(jié)果婚禮上恋昼,老公的妹妹穿的比我還像新娘看靠。我一直安慰自己,他們只是感情好液肌,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布挟炬。 她就那樣靜靜地躺著,像睡著了一般嗦哆。 火紅的嫁衣襯著肌膚如雪辟宗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天吝秕,我揣著相機(jī)與錄音泊脐,去河邊找鬼。 笑死烁峭,一個胖子當(dāng)著我的面吹牛容客,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播约郁,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼缩挑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鬓梅?” 一聲冷哼從身側(cè)響起供置,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绽快,沒想到半個月后芥丧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坊罢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年续担,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片活孩。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡物遇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情询兴,我是刑警寧澤乃沙,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站诗舰,受9級特大地震影響崔涂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜始衅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缭保。 院中可真熱鬧汛闸,春花似錦、人聲如沸艺骂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钳恕。三九已至别伏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忧额,已是汗流浹背厘肮。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留睦番,地道東北人类茂。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像托嚣,于是被迫代替她去往敵國和親巩检。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容