iOS之GCD學習心得

1.GCD簡介

gcd有兩大概念:任務和隊列
(1) 任務:同步任務和異步任務。
同步任務:不會開辟線程,在當前線程執行任務
異步任務:會開辟線程,在新的線程中執行任務
(2) 隊列:串行隊列和并行隊列
串行隊列:按任務順序執行
并行隊列:并發執行
(3)任務和隊列組合
同步串行:不會開辟新的線程,在當前線程按任務順序執行(沒意義,幾乎不用)
同步并行:不會開辟新的線程,在當前線程按任務順序執行 (幾乎不用)
異步串行:會開辟一條線程,在新線程中按任務順序執行
異步并行:會開辟多個子線程,在子線程中并發執行多個任務
同步主隊列:會發生死鎖
異步主隊列:不會開辟新的線程,任務按順序執行

2.代碼解析

(一)同步主隊列(死鎖)

- (void)syncMain {
   NSLog(@"start = %@", [NSThread currentThread]);
   dispatch_queue_t queue = dispatch_get_main_queue();
   dispatch_sync(queue, ^{
       NSLog(@"任務1%@",[NSThread currentThread]);
   });
   dispatch_sync(queue, ^{
       NSLog(@"任務2%@",[NSThread currentThread]);
   });
   dispatch_sync(queue, ^{
       NSLog(@"任務3%@",[NSThread currentThread]);
   });
   NSLog(@"end");
}

打印結果:

F1DF92B2-CFC3-42A5-A15D-CEA8C4EA3B67.png

原因:
同步:
1 ).不會開辟新線程;
2 ).上一個任務執行結束才會繼續往下執行。
結果:要想sync函數往下執行,必須等待block任務結束。
主隊列:
1 ).主隊列只能在主線程執行,不能再子線程執行;
2 ).主線程必須等待空閑的時候,才會執行下一個任務。
結果:一個線程只能執行一個任務,Block想要執行必須等待主線程空閑,而主線程在執行sync函數;所以要等待其結束才會執行。
同步主隊列:sync函數等待Block任務結束,Block任務等待sync函數結束,兩個任務互相等待,導致堵塞主線程,發生死鎖現象。

(二)異步主隊列

 - (void)asyncMain {
    NSLog(@"start = %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"任務1%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務3%@",[NSThread currentThread]);
    });
    NSLog(@"end");
}

打印結果:

B8D19A4D-144C-4378-8229-E5886FF06B1F.png

原因:
1.因為是異步,可以先繞過不執行,回頭再執行,所以先執行start和end
2.因為是主隊列,要在主線程中執行,所以不會開辟子線程
3.主隊列跟串行隊列一樣,任務都是按順序執行

(三)同步串行隊列

- (void)syncSerial {
    NSLog(@"start = %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
         NSLog(@"任務1%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務2%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務3%@",[NSThread currentThread]);
    });
    NSLog(@"end");
}

打印結果:

94533D5C-7B58-4B45-A272-C77B95E2CB01.png

原因:
1.同步任務:不會開辟新線程
2.串行隊列:按任務順序執行

(四)同步并行隊列

- (void)syncConcurrent {
    NSLog(@"start = %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"任務1%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務2%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務3%@",[NSThread currentThread]);
    });
    NSLog(@"end");
}

打印結果:

6AF01CD9-6DE9-4615-A31D-2A499B3D33AC.png

原因:跟同步串行一樣的道理(個人覺得同步串行和同步異行并沒有什么意義,基本上用不到)

(五)異步串行隊列

- (void)asyncSerial {
    NSLog(@"start = %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"任務1%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務3%@",[NSThread currentThread]);
    });
    NSLog(@"end");
}

打印結果:

C07F6AE8-4DA0-4D07-9427-2FE8862084BF.png

原因:
異步任務:會開辟新的線程,可以繞過任務不執行,回頭再執行
串行隊列:任務按順序執行
異步串行:只會開辟一個新線程,任務按順序執行

(六)異步并行隊列

- (void)asyncConcurrent {
    NSLog(@"start = %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任務1%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務3%@",[NSThread currentThread]);
    });
    NSLog(@"end");
}

打印結果:

216FDDB0-E2BC-4F0B-B4EA-B11E1C92F9B4.png

原因:
異步任務:會開辟新線程,可以繞過任務不執行,回頭再執行
并行隊列:任務并發執行
異步并行:會開辟多個子線程,任務并發執行

(七)全局隊列

- (void)asyncGlobal {
    NSLog(@"start = %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"任務1%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務3%@",[NSThread currentThread]);
    });
    NSLog(@"end");
}

打印結果:

F673DDC2-42F0-4320-802D-5EB904D89B8E.png

原因:
異步任務:會開辟新線程,可以繞過任務不執行,回頭再執行
全局隊列:跟并發隊列一樣,任務同時執行,不過全局隊列有優先級設置

3.應用場景

(1)比如加載一些圖片,處理大型數據等耗時操作,可以放在子線程中執行,在返回主線程刷新UI。

dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        //耗時操作...
        
        dispatch_async(dispatch_get_main_queue(), ^{
            //回到主線程,刷新UI
        });
    });

(2)gcd實現定時器

 NSInteger count = 0;
- (void)time {
    //注意事項:dispatch_source_t最好用全局,局部不加dispatch_cancel,定時器不會被執行,因為還沒到回調timer就被釋放了。
    //創建一個定時器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    //設置定時器
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    //設置回調
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"第%ld次執行",count);
        count ++;
        if (count > 6) {
            //取消定時器
            dispatch_cancel(timer);
        }
    });
    //啟動定時器
    dispatch_resume(timer);  
}

(3)gcd延遲執行

- (void)after {
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        // 3秒后異步執行這里的代碼...
        NSLog(@"after");
       
    });
    
}

(4)gcd只執行一次

- (void)once {
    for (int i = 0; i < 3; i ++) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"xxx");
        });
    }
}

(5)dispatch_apply,可以實現遍歷數組效果

- (void)apply {
    NSArray *arr = @[@"1",@"2",@"3",@"4",@"5",@"6"];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply([arr count], queue, ^(size_t index) {
        NSLog(@"%zu : %@",index,arr[index]);
    });
}

打印結果:

748497BB-AF35-4D9C-AAEF-09095719FA4F.png

(6)GCD的隊列組dispatch_group,等多個異步操作結束后,再回到主線程

- (void)group {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
        NSLog(@"第一個耗時任務%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"第二個耗時任務%@",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"回到主線程%@",[NSThread currentThread]);
    });
}

打印結果:

D27BCD2C-68A8-41D1-820F-620B74A3AB0D.png

(7)柵欄方法 dispatch_barrier_async,可以分割異步線程順序

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任務1%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
         NSLog(@"任務分割%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務3%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務4%@",[NSThread currentThread]);
    });
   
}

打印結果:

D72914AB-11DE-4C67-B8EF-A0583611FA6F.png

(8)信號量 dispatch_semaphore_t
應用場景:假如有多個網絡請求,我們要求按順序執行,也就是網絡1請求結束之后再請求網絡2,以此類推。。。
由于網絡請求是異步的,想要其同步執行該怎么實現呢?有的人就會想說:我在網絡請求1結束回調里請求網絡2,再在網絡2請求結束里請求網絡3,這當然可以實現,但是這種方法對于少數請求還好,假如有10個,100個你還這樣寫,不說代碼量,就是看上去都會覺得很low。這時候信號量就派上用場了,看代碼:

    NSLog(@"start");
    dispatch_semaphore_t sema= dispatch_semaphore_create(0);
    dispatch_async(dispatch_queue_create("d", DISPATCH_QUEUE_CONCURRENT), ^{
        for (int i = 0; i < 10; i ++) {
            [[BPNetworkTool sharedTools] GET:@"http://s.budejie.com/topic/list/zuixin/41/bs0315-iphone-4.5.6/0-20.json" parameters:nil success:^(id obj) {
                NSLog(@"%d",i);
                dispatch_semaphore_signal(sema);
                
            } failure:^(NSError *error) {
                
            }];
            dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"end");
            
        });
        
    });

打印結果:


781145BA-280D-4E46-94FE-551CB1EC4781.png

我們從代碼中看到,信號量用到了三個方法:
1.dispatch_semaphore_t sema= dispatch_semaphore_create(0);
2. dispatch_semaphore_signal(sema);
3.dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

方法一:代表先創建一個信號量,下面的參數代表信號量的個數。
方法二:表示發送一個信號,信號量+1。
方法三:表示等待信號,第二個參數表示等待時間,當信號數量少于0時會一直等待,反之可以繼續執行下面方法,并且信號量-1。

(9)suspend/resume(隊列掛起和恢復)
suspend: 通過 dispatch_suspend() 函數實現隊列的”掛起”,使隊列暫停工作。但是這里的“掛起”,并不能立即停止隊列上正在運行的block;

resume: dispatch_resume() 函數恢復隊列,是隊列繼續工作。

注意:
    1. dispatch_suspend 與 dispatch_resume 要成對出現。
    2.dispatch_suspend在前,dispatch_resume在后。

代碼實現:

dispatch_queue_t queue = dispatch_queue_create("test", NULL);
    for (int i = 0; i < 5; i ++) {
        dispatch_async(queue, ^{
            NSLog(@"任務%d開始",i);
            sleep(3);
            NSLog(@"任務%d結束",i);
        });
    }
    NSLog(@"任務創建完成");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        dispatch_suspend(queue);
        NSLog(@"隊列掛起");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            dispatch_resume(queue);
            NSLog(@"隊列恢復");
        });
    });

打印結果:

2017-12-06 17:14:59.740 GCD[6995:1631360] 任務創建完成
2017-12-06 17:14:59.740 GCD[6995:1631625] 任務0開始
2017-12-06 17:15:02.746 GCD[6995:1631625] 任務0結束
2017-12-06 17:15:02.746 GCD[6995:1631625] 任務1開始
2017-12-06 17:15:05.747 GCD[6995:1631625] 任務1結束
2017-12-06 17:15:05.747 GCD[6995:1631625] 任務2開始
2017-12-06 17:15:06.741 GCD[6995:1631360] 隊列掛起
2017-12-06 17:15:08.750 GCD[6995:1631625] 任務2結束
2017-12-06 17:15:12.237 GCD[6995:1631360] 隊列恢復
2017-12-06 17:15:12.237 GCD[6995:1631625] 任務3開始
2017-12-06 17:15:15.242 GCD[6995:1631625] 任務3結束
2017-12-06 17:15:15.243 GCD[6995:1631625] 任務4開始
2017-12-06 17:15:18.243 GCD[6995:1631625] 任務4結束

通過打印結果我們可以驗證當調用 dispatch_suspend(queue) “掛起”隊列 queue 后已經開始執行的任務不會掛起,而未開始的任務可以掛起。

(10) dispatch_set_target_queue (更改隊列的類型)
不管是串行隊列還是并行隊列都可以將其改為串行隊列。
使用的函數:dispatch_set_target_queue(dispatch_object_t object,
dispatch_queue_t _Nullable queue);
第一個參數:是指要更改優先級的隊列。
第二個參數:目標參照物,將要更改的隊列優先級與其相同。

代碼實現:

dispatch_queue_t queue1 = dispatch_queue_create("test1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("test2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue3 = dispatch_queue_create("test3", DISPATCH_QUEUE_SERIAL);
    dispatch_set_target_queue(queue1, queue3);
    dispatch_set_target_queue(queue2, queue3);
    dispatch_async(queue1, ^{
        NSLog(@"任務1");
    });
    dispatch_async(queue2, ^{
        NSLog(@"任務2");
    });
    dispatch_async(queue3, ^{
        NSLog(@"任務3");
    });

打印結果:

2017-12-06 17:22:48.605 GCD[7162:1758848] 任務1
2017-12-06 17:22:48.606 GCD[7162:1758848] 任務2
2017-12-06 17:22:48.606 GCD[7162:1758848] 任務3

從打印結果可以看出用dispatch_set_target_queue()函數可以將并行隊列和串行隊列,改成串行隊列。

未完待續。。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容