GCD---關於iOS多線程

GCD

GCD,全名Grand Central Dispatch,中文名郭草地,是基於C語言的一套多線程開發API,一聽名字就是個狠角色,也是目前蘋果官方推薦的多線程開發方式。可以說是使用方便,又不失逼格。總體來說,他解決我提到的上面直接操作線程帶來的難題,它自動幫你管理了線程的生命週期以及任務的執行規則。下面我們會頻繁的說道一個詞,那就是任務,說白了,任務其實就是你要執行的那段代碼。

任務管理方式——隊列

上面說當我們要管理多個任務時,線程開發給我們帶來了一定的技術難度,或者說不方便性,GCD給出了我們統一管理任務的方式,那就是隊列。我們來看一下iOS多線程操作中的隊列:(??不管是串行還是並行,隊列都是按照FIFO的原則依次觸發任務)

兩個通用隊列:

  • 串行隊列:所有任務會在一條線程中執行(有可能是當前線程也有可能是新開闢的線程),並且一個任務執行完畢後,纔開始執行下一個任務。(等待完成)

  • 並行隊列:可以開啓多條線程並行執行任務(但不一定會開啓新的線程),並且當一個任務放到指定線程開始執行時,下一個任務就可以開始執行了。(等待發生)

兩個特殊隊列:

  • 主隊列:系統爲我們創建好的一個串行隊列,牛逼之處在於它管理必須在主線程中執行的任務,屬於有勞保的。

  • 全局隊列:系統爲我們創建好的一個並行隊列,使用起來與我們自己創建的並行隊列無本質差別。

任務執行方式

說完隊列,相應的,任務除了管理,還得執行,要不然有錢不花,掉了白搭,並且在GCD中並不能直接開闢線程執行任務,所以在任務加入隊列之後,GCD給出了兩種執行方式——同步執行(sync)和異步執行(async)。

  • 同步執行:在當前線程執行任務,不會開闢新的線程。必須等到Block函數執行完畢後,dispatch函數纔會返回。

  • 異步執行:可以在新的線程中執行任務,但不一定會開闢新的線程。dispatch函數會立即返回, 然後Block在後臺異步執行。

上面的這些理論都是本人在無數被套路背後總結出來的血淋淋的經驗,與君共享,但是這麼寫我猜你一定還是不明白,往下看,說不定有驚喜呢

4408163-c2a5cb92cc9a187c.jpg

任務隊列組合方式

相信這個標題你看過無數次?是不是看完也不知道到底怎麼用?這麼巧,我也是,請相信下面這些肯定有你不知道並且想要的,我們從兩個最直接的點切入:

1. 線程死鎖

這個你是不是也看過無數次?哈哈哈!你是不是覺得我又要開始複製黏貼了?請往下看:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

運行結果:

打印結果:

1
ThreadDemo[5615:874679] 1========{number = 1, name = main}

真不是我套路你,我們還是得分析一下爲什麼會死鎖,因爲總得爲那些沒有飽受過套路的人心裏留下一段美好的回憶,分享代碼,我們是認真的!

事情是這樣的:

我們先做一個定義:- (void)viewDidLoad{} ---> 任務A,GCD同步函數 --->任務B。

總而言之呢,大概是這樣的,首先,任務A在主隊列,並且已經開始執行,在主線程打印出1===... ...,然後這時任務B被加入到主隊列中,並且同步執行,這尼瑪事都大了,系統說,同步執行啊,那我不開新的線程了,任務B說我要等我裏面的Block函數執行完成,要不我就不返回,但是主隊列說了,玩蛋去,我是串行的,你得等A執行完才能輪到你,不能壞了規矩,同時,任務B作爲任務A的內部函數,必須等任務B執行完函數返回才能執行下一個任務。那就造成了,任務A等待任務B完成才能繼續執行,但作爲串行隊列的主隊列又不能讓任務B在任務A未完成之前開始執行,所以任務A等着任務B完成,任務B等着任務A完成,等待,永久的等待。所以就死鎖了。簡單不?下面我們慎重看一下我們無意識書寫的代碼!

2. 這樣不死鎖

不如就寫個最簡單的:

1
2
3
4
5
6
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    NSLog(@"2========%@",[NSThread currentThread]);
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印結果:

1
2
3
ThreadDemo[5803:939324] 1========{number = 1, name = main}
ThreadDemo[5803:939324] 2========{number = 1, name = main}
ThreadDemo[5803:939324] 3========{number = 1, name = main}

之前有人問:順序打印,沒毛病,全在主線程執行,而且順序執行,那它們一定是在主隊列同步執行的啊!那爲什麼沒有死鎖?蘋果的操作系統果然高深啊!

4408163-6298500c99aca848.jpg

其實這裏有一個誤區,那就是任務在主線程順序執行就是主隊列。其實一點關係都沒有,如果當前在主線程,同步執行任務,不管在什麼隊列任務都是順序執行。把所有任務都以異步執行的方式加入到主隊列中,你會發現它們也是順序執行的。而且任務的執行不一定非得撕扯白咧的加入到隊列中纔可以啊!

相信你知道上面的死鎖情況後,你一定會手賤改成這樣試試:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印結果:

1
2
3
ThreadDemo[5830:947858] 1========{number = 1, name = main}
ThreadDemo[5830:947858] 2========{number = 1, name = main}
ThreadDemo[5830:947858] 3========{number = 1, name = main}

你發現正常執行了,並且是順序執行的,你是不是若有所思,沒錯,你想的和我想的是一樣的,和上訴情況一樣,任務A在主隊列中,但是任務B加入到了全局隊列,這時候,任務A和任務B沒有隊列的約束,所以任務B就先執行嘍,執行完畢之後函數返回,任務A接着執行。

我猜你一定手賤這麼改過:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印結果:

1
2
3
ThreadDemo[5911:962470] 1========{number = 1, name = main}
ThreadDemo[5911:962470] 3========{number = 1, name = main}
ThreadDemo[5911:962470] 2========{number = 1, name = main}

細心而帥氣的你一定發現不是順序打印了,而且也不會死鎖,明明都是加到主隊列裏了啊,其實當任務A在執行時,任務B加入到了主隊列,注意哦,是異步執行,所以dispatch函數不會等到Block執行完成才返回,dispatch函數返回後,那任務A可以繼續執行,Block任務我們可以認爲在下一幀順序加入隊列,並且默認無限下一幀執行。這就是爲什麼你看到2===... ...是最後輸出的了。(??一個函數的有多個內部函數異步執行時,不會造成死鎖的同時,任務A執行完畢後,這些異步執行的內部函數會順序執行)。

我們說說隊列與執行方式的搭配

上面說了系統自帶的兩個隊列,下面我們來用自己創建的隊列研究一下各種搭配情況。

我們先創建兩個隊列,並且測試方法都是在主線程中調用:

1
2
3
4
//串行隊列
self.serialQueue = dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL);
//並行隊列
self.concurrentQueue = dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT);

1. 串行隊列 + 同步執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)queue_taskTest{
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印結果:

1
2
3
4
ThreadDemo[6735:1064390] 1========{number = 1, name = main}
ThreadDemo[6735:1064390] 2========{number = 1, name = main}
ThreadDemo[6735:1064390] 3========{number = 1, name = main}
ThreadDemo[6735:1064390] 4========{number = 1, name = main}

全部都在當前線程順序執行,也就是說,同步執行不具備開闢新線程的能力。

2. 串行隊列 + 異步執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)queue_taskTest{
    dispatch_async(self.serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印結果:

1
2
3
4
ThreadDemo[6774:1073235] 4========{number = 1, name = main}
ThreadDemo[6774:1073290] 1========{number = 3, name = (null)}
ThreadDemo[6774:1073290] 2========{number = 3, name = (null)}
ThreadDemo[6774:1073290] 3========{number = 3, name = (null)}

先打印了4,然後順序在子線程中打印1,2,3。說明異步執行具有開闢新線程的能力,並且串行隊列必須等到前一個任務執行完才能開始執行下一個任務,同時,異步執行會使內部函數率先返回,不會與正在執行的外部函數發生死鎖。

3. 並行隊列 + 同步執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)queue_taskTest{
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

運行結果:

1
2
3
4
ThreadDemo[7012:1113594] 1========{number = 1, name = main}
ThreadDemo[7012:1113594] 2========{number = 1, name = main}
ThreadDemo[7012:1113594] 3========{number = 1, name = main}
ThreadDemo[7012:1113594] 4========{number = 1, name = main}

未開啓新的線程執行任務,並且Block函數執行完成後dispatch函數纔會返回,才能繼續向下執行,所以我們看到的結果是順序打印的。

4. 並行隊列 + 異步執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)queue_taskTest{
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印結果:

1
2
3
4
ThreadDemo[7042:1117492] 1========{number = 3, name = (null)}
ThreadDemo[7042:1117491] 3========{number = 5, name = (null)}
ThreadDemo[7042:1117451] 4========{number = 1, name = main}
ThreadDemo[7042:1117494] 2========{number = 4, name = (null)}

開闢了多個線程,觸發任務的時機是順序的,但是我們看到完成任務的時間卻是隨機的,這取決於CPU對於不同線程的調度分配,但是,線程不是無條件無限開闢的,當任務量足夠大時,線程是會重複利用的。

劃一下重點啊

1. 對於單核CPU來說,不存在真正意義上的並行,所以,多線程執行任務,其實也只是一個人在幹活,CPU的調度決定了非等待任務的執行速率,同時對於非等待任務,多線程並沒有真正意義提高效率。

2. 線程可以簡單的認爲就是一段代碼+運行時數據。

3. 同步執行會在當前線程執行任務,不具備開闢線程的能力或者說沒有必要開闢新的線程。並且,同步執行必須等到Block函數執行完畢,dispatch函數纔會返回,從而阻塞同一串行隊列中外部方法的執行。

4. 異步執行dispatch函數會直接返回,Block函數我們可以認爲它會在下一幀加入隊列,並根據所在隊列目前的任務情況無限下一幀執行,從而不會阻塞當前外部任務的執行。同時,只有異步執行纔有開闢新線程的必要,但是異步執行不一定會開闢新線程。

5. 只要是隊列,肯定是FIFO(先進先出),但是誰先執行完要看第1條。

6. 只要是串行隊列,肯定要等上一個任務執行完成,才能開始下一個任務。但是並行隊列當上一個任務開始執行後,下一個任務就可以開始執行。

7. 想要開闢新線程必須讓任務在異步執行,想要開闢多個線程,只有讓任務在並行隊列中異步執行纔可以。執行方式和隊列類型多層組合在一定程度上能夠實現對於代碼執行順序的調度。

8. 同步+串行:未開闢新線程,串行執行任務;同步+並行:未開闢新線程,串行執行任務;異步+串行:新開闢一條線程,串行執行任務;異步+並行:開闢多條新線程,並行執行任務;在主線程中同步使用主隊列執行任務,會造成死鎖。

8. 對於多核CPU來說,線程數量也不能無限開闢,線程的開闢同樣會消耗資源,過多線程同時處理任務並不是你想像中的人多力量大。

GCD其他函數用法

1. dispatch_after

該函數用於任務延時執行,其中參數dispatch_time_t代表延時時長,dispatch_queue_t代表使用哪個隊列。如果隊列未主隊列,那麼任務在主線程執行,如果隊列爲全局隊列或者自己創建的隊列,那麼任務在子線程執行,代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(void)GCDDelay{
    //主隊列延時
    dispatch_time_t when_main = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(when_main, dispatch_get_main_queue(), ^{
        NSLog(@"main_%@",[NSThread currentThread]);
    });
    //全局隊列延時
    dispatch_time_t when_global = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
    dispatch_after(when_global, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"global_%@",[NSThread currentThread]);
    });
    //自定義隊列延時
    dispatch_time_t when_custom = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    dispatch_after(when_custom, self.serialQueue, ^{
        NSLog(@"custom_%@",[NSThread currentThread]);
    });
}

打印結果:

1
2
3
ThreadDemo[1508:499647] main_{number = 1, name = main}
ThreadDemo[1508:499697] global_{number = 3, name = (null)}
ThreadDemo[1508:499697] custom_{number = 3, name = (null)}

2. dispatch_once

保證函數在整個生命週期內只會執行一次,看代碼。

1
2
3
4
5
6
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

打印結果:

1
ThreadDemo[1524:509261] {number = 1, name = main}
1
無論你怎麼瘋狂的點擊,在第一次打印之後,輸出臺便巋然不動。

3. dispatch_group_async & dispatch_group_notify

試想,現在牛逼的你要現在兩張小圖,並且你要等兩張圖都下載完成之後把他們拼起來,你要怎麼做?我根本就不會把兩張圖拼成一張圖啊,牛逼的我怎麼可能有這種想法呢?

4408163-4234d15fcfc56bcd.jpg

其實方法有很多,比如你可以一張一張下載,再比如使用局部變量和Blcok實現計數,但是既然今天我們講到這,那我們就得入鄉隨俗,用GCD來實現,有一個神器的東西叫做隊列組,當加入到隊列組中的所有任務執行完成之後,會調用dispatch_group_notify函數通知任務全部完成,代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-(void)GCDGroup{
    //
    [self jointImageView];
    //
    dispatch_group_t group = dispatch_group_create();
    __block UIImage *image_1 = nil;
    __block UIImage *image_2 = nil;
    //在group中添加一個任務
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image_2 = [self imageWithPath:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947,2002573948&fm=26&gp=0.jpg"];
    });
    //group中所有任務執行完畢,通知該方法執行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        self.imageView_1.image = image_1;
        self.imageView_2.image = image_2;
        //
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0f);
        [image_2 drawInRect:CGRectMake(0, 0, 100, 100)];
        [image_1 drawInRect:CGRectMake(100, 0, 100, 100)];
        UIImage *image_3 = UIGraphicsGetImageFromCurrentImageContext();
        self.imageView_3.image = image_3;
        UIGraphicsEndImageContext();
    });
}
-(void)jointImageView{
    self.imageView_1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];
    [self.view addSubview:_imageView_1];
     
    self.imageView_2 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 50, 100, 100)];
    [self.view addSubview:_imageView_2];
     
    self.imageView_3 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 200, 100)];
    [self.view addSubview:_imageView_3];
     
    self.imageView_1.layer.borderColor = self.imageView_2.layer.borderColor = self.imageView_3.layer.borderColor = [UIColor grayColor].CGColor;
    self.imageView_1.layer.borderWidth = self.imageView_2.layer.borderWidth = self.imageView_3.layer.borderWidth = 1;
}

4. dispatch_barrier_async

柵欄函數,這麼看來它能擋住或者分隔什麼東西,別瞎猜了,反正你又猜不對,看這,使用此方法創建的任務,會查找當前隊列中有沒有其他任務要執行,如果有,則等待已有任務執行完畢後再執行,同時,在此任務之後進入隊列的任務,需要等待此任務執行完成後,才能執行。看代碼,老鐵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務2");
    });
     
//    dispatch_barrier_async(self.concurrentQueue, ^{
//        NSLog(@"任務barrier");
//    });
     
//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務4");
    });
}

運行結果:

1
2
3
4
ThreadDemo[1816:673351] 任務3
ThreadDemo[1816:673353] 任務1
ThreadDemo[1816:673350] 任務2
ThreadDemo[1816:673370] 任務4

是不是如你所料,牛逼大了,下面我們打開第一句註釋:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務2");
    });
     
    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任務barrier");
    });
     
//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務4");
    });
}

打印結果:

1
2
3
4
5
ThreadDemo[1833:678739] 任務2
ThreadDemo[1833:678740] 任務1
ThreadDemo[1833:678740] 任務barrier
ThreadDemo[1833:678740] 任務3
ThreadDemo[1833:678739] 任務4

這個結果和我們上面的解釋完美契合,我們可以簡單的控制函數執行的順序了,你離大牛又近了一步,如果現在的你不會懷疑還有dispatch_barrier_sync這個函數的話,說明... ...嘿嘿嘿,我們看一下這個函數和上面我們用到的函數的區別,你一定想到了,再打開第二個和第三個註釋,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-(void)GCDbarrier{        
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務2");
    });
     
    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任務barrier");
    });
     
    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務4");
    });
}

運行結果:

1
2
3
4
5
6
7
ThreadDemo[1853:692434] 任務1
ThreadDemo[1853:692421] 任務2
ThreadDemo[1853:692387] big
ThreadDemo[1853:692421] 任務barrier
ThreadDemo[1853:692387] apple
ThreadDemo[1853:692421] 任務3
ThreadDemo[1853:692434] 任務4

不要着急,我們換一下函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-(void)GCDbarrier{
     
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務2");
    });
     
    dispatch_barrier_sync(self.concurrentQueue, ^{
        NSLog(@"任務barrier");
    });
     
    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任務4");
    });
}

打印結果:

1
2
3
4
5
6
7
ThreadDemo[1874:711841] 任務1
ThreadDemo[1874:711828] 任務2
ThreadDemo[1874:711793] 任務barrier
ThreadDemo[1874:711793] big
ThreadDemo[1874:711793] apple
ThreadDemo[1874:711828] 任務3
ThreadDemo[1874:711841] 任務4

老鐵,發現了嗎?這兩個函數對於隊列的柵欄作用是一樣的,但是對於該函數相對於其他內部函數遵循了最開始說到的同步和異步的規則。你是不是有點懵逼,如果你矇蔽了,那麼請在每一個輸出後面打印出當前的線程,如果你還是懵逼,那麼請你重新看,有勞,不謝!

5. dispatch_apply

該函數用於重複執行某個任務,如果任務隊列是並行隊列,重複執行的任務會併發執行,如果任務隊列爲串行隊列,則任務會順序執行,需要注意的是,該函數爲同步函數,要防止線程阻塞和死鎖哦,老鐵。

串行隊列:

1
2
3
4
5
6
-(void)GCDApply{
    //重複執行
    dispatch_apply(5, self.serialQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

運行結果:

1
2
3
4
5
ThreadDemo[1446:158101] 第0次_{number = 1, name = main}
ThreadDemo[1446:158101] 第1次_{number = 1, name = main}
ThreadDemo[1446:158101] 第2次_{number = 1, name = main}
ThreadDemo[1446:158101] 第3次_{number = 1, name = main}
ThreadDemo[1446:158101] 第4次_{number = 1, name = main}

並行隊列:

1
2
3
4
5
6
-(void)GCDApply{
    //重複執行
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

運行結果:

1
2
3
4
5
ThreadDemo[1461:160567] 第2次_{number = 4, name = (null)}
ThreadDemo[1461:160534] 第0次_{number = 1, name = main}
ThreadDemo[1461:160566] 第3次_{number = 5, name = (null)}
ThreadDemo[1461:160569] 第1次_{number = 3, name = (null)}
ThreadDemo[1461:160567] 第4次_{number = 4, name = (null)}

死鎖:

1
2
3
4
5
6
-(void)GCDApply{
    //重複執行
    dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

運行結果:

6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

看這幾個函數的時候你需要拋開隊列,丟掉同步異步,不要把它們想到一起,混爲一談,信號量只是控制任務執行的一個條件而已,相對於上面通過隊列以及執行方式來控制線程的開闢和任務的執行,它更貼近對於任務直接的控制。類似於單個隊列的最大併發數的控制機制,提高並行效率的同時,也防止太多線程的開闢對CPU早層負面的效率負擔。

dispatch_semaphore_create創建信號量,初始值不能小於0;

dispatch_semaphore_wait等待降低信號量,也就是信號量-1;

dispatch_semaphore_signal提高信號量,也就是信號量+1;

dispatch_semaphore_wait和dispatch_semaphore_signal通常配對使用。

看一下代碼吧,老鐵。

1
2
3
4
5
6
7
8
9
10
11
-(void)GCDSemaphore{
    //
    //dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        //dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            //dispatch_semaphore_signal(semaphore);
        });
    });
}

你能猜到運行結果嗎?沒錯,就是你想的這樣,開闢了5個線程執行任務。

1
2
3
4
5
ThreadDemo[1970:506692] 第0次_{number = 3, name = (null)}
ThreadDemo[1970:506711] 第1次_{number = 4, name = (null)}
ThreadDemo[1970:506713] 第2次_{number = 5, name = (null)}
ThreadDemo[1970:506691] 第3次_{number = 6, name = (null)}
ThreadDemo[1970:506694] 第4次_{number = 7, name = (null)}

下一步你一定猜到了,把註釋的代碼打開:

1
2
3
4
5
6
7
8
9
10
11
-(void)GCDSemaphore{
    //
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
    });
}

運行結果:

1
2
3
4
5
ThreadDemo[2020:513651] 第0次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第1次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第2次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第3次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第4次_{number = 3, name = (null)}

很明顯,我開始說的是對的,哈哈哈哈,信號量是控制任務執行的重要條件,當信號量爲0時,所有任務等待,信號量越大,允許可並行執行的任務數量越多。

GCD就先說到這,很多API沒有涉及到,有興趣的同學們可以自己去看看,重要的是方法和習慣,而不是你看過多少

原文:http://www.cocoachina.com/ios/20170829/20404.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章