iOS - 多線程之GCD

簡介

GCD, Grand Central Dispatch, 可譯爲”強大的中樞調度器”, 是一種異步執行任務技術。基於libdispatch, 純C語言實現。(環境:Mac OS X 10.6 +, iOS 4+)

爲什麼要用 GCD

  • GCD 可用於多核的並行運算,會自動利用更多的 CPU 內核
  • GCD 會自動管理線程的生命週期(創建線程、調度任務、銷燬線程)

基本概念

隊列

Dispatch Queue是執行處理的等待隊列。
執行任務的等待隊列,即用來存放任務的隊列。隊列是一種特殊的線性表,採用 FIFO(先進先出)的原則,即新任務總是被插入到隊列的末尾,而讀取任務的時候總是從隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務。Dispatch Queue按照是否等待處理可以分爲serial Dispatch Queue和Concurrent Dispatch Queue。

串行隊列(serial Dispatch Queue)

每次只有一個任務被執行,任務一個接着一個地執行。

並行隊列(Concurrent Dispatch Queue)

在同一時間可以有多個任務被執行。

同步 (Synchronous)

在當前線程中執行任務,不具備開啓新線程的能力。提交的任務在執行完成後纔會返回。同步函數: dispatch_sync()。

異步 (Asynchronous)

在新線程中執行任務,具備開啓新線程的能力。提交的任務立刻返回,在後臺隊列中執行。異步函數: dispatch_async()。

GCD的使用步驟和組合方式

  1. 創建一個隊列(串行隊列或併發隊列)
  2. 將任務追加到任務的等待隊列中,然後系統就會根據任務類型執行任務(同步執行或異步執行)
串行隊列 並行隊列 主隊列
同步執行 不開啓新線程,串行執行任務 不開啓新線程,串行執行任務 死鎖
異步執行 開啓新線程(1條),串行執行任務 開啓新線程,併發執行任務 不開啓新線程,串行執行任務
同步執行 +串行隊列
    dispatch_queue_t serialQueue = dispatch_queue_create("com.jun.serial",DISPATCH_QUEUE_SERIAL);
    NSLog(@"線程信息%@",[NSThread currentThread]);
    dispatch_sync(serialQueue,^{
        NSLog(@"1-線程信息%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue,^{
        NSLog(@"2-線程信息%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue,^{
        NSLog(@"3-線程信息%@",[NSThread currentThread]);
    });

執行結果
2019-11-11 11:52:12.024148+0800 iosTest[11225:645870] 線程信息<NSThread: 0x6000012b3040>{number = 1, name = main}
2019-11-11 11:52:12.024324+0800 iosTest[11225:645870] 1-線程信息<NSThread: 0x6000012b3040>{number = 1, name = main}
2019-11-11 11:52:12.024410+0800 iosTest[11225:645870] 2-線程信息<NSThread: 0x6000012b3040>{number = 1, name = main}
2019-11-11 11:52:12.024497+0800 iosTest[11225:645870] 3-線程信息<NSThread: 0x6000012b3040>{number = 1, name = main}

同步執行 + 並行隊列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jun.concurrent",DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"線程信息%@",[NSThread currentThread]);
    dispatch_sync(concurrentQueue,^{
        NSLog(@"1-線程信息%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue,^{
        NSLog(@"2-線程信息%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue,^{
        NSLog(@"3-線程信息%@",[NSThread currentThread]);
    });

執行結果
2019-11-11 11:55:08.379352+0800 iosTest[11279:647739] 線程信息<NSThread: 0x600001968540>{number = 1, name = main}
2019-11-11 11:55:08.379479+0800 iosTest[11279:647739] 1-線程信息<NSThread: 0x600001968540>{number = 1, name = main}
2019-11-11 11:55:08.379599+0800 iosTest[11279:647739] 2-線程信息<NSThread: 0x600001968540>{number = 1, name = main}
2019-11-11 11:55:08.379695+0800 iosTest[11279:647739] 3-線程信息<NSThread: 0x600001968540>{number = 1, name = main}

同步執行 + 主隊列

在主隊列中執行同步操作會引起死鎖。

異步執行 + 串行隊列
    dispatch_queue_t serialQueue = dispatch_queue_create("com.jun.serial",DISPATCH_QUEUE_SERIAL);
    NSLog(@"線程信息%@",[NSThread currentThread]);
    dispatch_async(serialQueue,^{
        NSLog(@"1-線程信息%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue,^{
        NSLog(@"2-線程信息%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue,^{
        NSLog(@"3-線程信息%@",[NSThread currentThread]);
    });

執行結果
2019-11-11 11:56:33.786021+0800 iosTest[11307:648849] 線程信息<NSThread: 0x600001d2e940>{number = 1, name = main}
2019-11-11 11:56:33.786268+0800 iosTest[11307:648902] 1-線程信息<NSThread: 0x600001d20240>{number = 3, name = (null)}
2019-11-11 11:56:33.786353+0800 iosTest[11307:648902] 2-線程信息<NSThread: 0x600001d20240>{number = 3, name = (null)}
2019-11-11 11:56:33.786454+0800 iosTest[11307:648902] 3-線程信息<NSThread: 0x600001d20240>{number = 3, name = (null)}

異步執行 + 並行隊列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jun.concurrent",DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"線程信息%@",[NSThread currentThread]);
    dispatch_async(concurrentQueue,^{
        sleep(3);
        NSLog(@"1-線程信息%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue,^{
        sleep(1);
        NSLog(@"2-線程信息%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue,^{
        sleep(2);
        NSLog(@"3-線程信息%@",[NSThread currentThread]);
    });

執行結果
2019-11-11 11:58:06.329150+0800 iosTest[11338:650092] 線程信息<NSThread: 0x600001b78e00>{number = 1, name = main}
2019-11-11 11:58:07.333266+0800 iosTest[11338:650148] 2-線程信息<NSThread: 0x600001b00500>{number = 3, name = (null)}
2019-11-11 11:58:08.331958+0800 iosTest[11338:650150] 3-線程信息<NSThread: 0x600001b09540>{number = 4, name = (null)}
2019-11-11 11:58:09.332356+0800 iosTest[11338:650149] 1-線程信息<NSThread: 0x600001b001c0>{number = 5, name = (null)}

異步執行 + 主隊列
    dispatch_async(dispatch_get_main_queue(),^{
        NSLog(@"更新UI");
    });

其他常用方法

dispatch_after
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"延遲3秒執行");
    });

對於時間要求不嚴格的可以使用這個方法,因爲3秒只是block追到到主隊列中的時間。如果主隊列中有大量操作需要處理,會延遲操作的執行時間。第一個參數是指定的dispatch_time_t類型。該值有dispatch_time或dispatch_walltime。前者通常用於相對時間,後者爲絕對時間。如果對時間精度要求高建議使用NSTimer。

dispatch_group_xxx
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"操作1");
    });
    dispatch_group_async(group, queue, ^{
        sleep(3);
        NSLog(@"操作2");
    });
    dispatch_notify(group, queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"耗時操作完成,更新UI");
        });
    });

dispatch_notify用於前面group中加入的block執行完後通知執行處理結果。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"操作1");
    });
    dispatch_group_async(group, queue, ^{
        sleep(3);
        NSLog(@"操作2");
    });

    dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
    
    NSLog(@"操作1和操作2完成之後纔會執行");

暫停當前線程(阻塞當前線程),等待指定的 group 中的任務執行完成後,纔會往下繼續執行。

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue,^{
        NSLog(@"操作1");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue,^{
        sleep(8);
        NSLog(@"操作2");
        dispatch_group_leave(group);
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"end");

dispatch_group_enter、dispatch_group_leave組合等同於dispatch_group_async。

dispatch_once
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"只執行一次的代碼");
    });

dispatch_once 保證 block 只會被執行一次,一般用於單例模式中初始化 static 的單例對象。

dispatch_barrier_xxx
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jun.concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        sleep(8);
        NSLog(@"操作1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"操作2");
    });
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"操作3");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"操作4");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"操作5");
    });

只有在操作1、操作2完成之後 纔會執行後續的操作。barrier就像一個柵欄,把queue分爲barrier之前和barrier之後。先執行barrier之前的,再執行barrier之中的,最後執行barrier之後的。

dispatch_apply
    dispatch_queue_t globarQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(3,globarQueue,^(size_t index) {
        NSLog(@"%zu",index);
    });
    NSLog(@"end");

打印結果
2019-11-11 13:45:18.772546+0800 iosTest[12458:705620] 0
2019-11-11 13:45:18.772548+0800 iosTest[12458:705681] 2
2019-11-11 13:45:18.772549+0800 iosTest[12458:705682] 1
2019-11-11 13:45:18.772683+0800 iosTest[12458:705620] end

開啓多條線程,併發執行,相比於for循環在耗時操作中極大的提高效率和速度。

dispatch_semaphore_xxx

可以藉助信號量控制同時進行任務的數量。

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jun.concurrent",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue,^{
        NSLog(@"操作1");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue,^{
        sleep(10);
        NSLog(@"操作2");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue,^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"操作3");
    });

dispatch_semaphore_create:創建一個semaphore並初始化信號的總量。
dispatch_semaphore_signal:發送一個信號,讓信號總量加1。
dispatch_semaphore_wait:可以使總信號量減1,當信號總量爲0時就會一直等待(阻塞所在線程),否則就可以正常執行。
semaphore 是持有計數的信號,計數爲0時等待,計數爲1或者大於1,調用dispatch_semaphore_wait減1不繼續等待。

dispatch_block_XXX

dispatch_block_wait會阻塞當前線程,並等待前面的任務執行完畢。
dispatch_block_notify 不會阻塞當前線程,會在指定的 block 執行結束後將指定 block 插入到指定的 queue 中。

    dispatch_queue_t serialQueue = dispatch_queue_create("com.jun.serial",DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block1 = dispatch_block_create(0, ^{
        NSLog(@"start block1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end block1");
    });

    dispatch_async(serialQueue, block1);
    long result = dispatch_wait(block1, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
    if (result == 0) {
        NSLog(@"success perform block1");
    } else {
        NSLog(@"time out");
    }
    
    dispatch_block_t block2 = dispatch_block_create(0, ^{
        NSLog(@"start block2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end block2");
    });
    dispatch_async(serialQueue, block2);
    dispatch_block_wait(block1, DISPATCH_TIME_FOREVER);
    dispatch_block_notify(block1,queue1,block2);
    dispatch_block_cancel(block2);
dispatch_set_target_queue
    dispatch_queue_t targetQueue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_set_target_queue(targetQueue, globalQueue);
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"queue1 1");
    });
    dispatch_async(targetQueue, ^{
        NSLog(@"async");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 1");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 2");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 3");
    });
    dispatch_async(queue1, ^{
        NSLog(@"queue1 2");
    });
    dispatch_async(queue1, ^{
        sleep(1);
        NSLog(@"queue1 3");
    });

輸出結果
2019-11-25 16:53:57.804485+0800 iosTest[1981:130563] queue1 1
2019-11-25 16:53:57.804573+0800 iosTest[1981:130563] queue1 2
2019-11-25 16:53:58.808788+0800 iosTest[1981:130563] queue1 3
2019-11-25 16:53:58.808959+0800 iosTest[1981:130563] async
2019-11-25 16:53:58.809040+0800 iosTest[1981:130563] queue2 1
2019-11-25 16:53:58.809106+0800 iosTest[1981:130563] queue2 2
2019-11-25 16:53:58.809206+0800 iosTest[1981:130563] queue2 3

dispatch_suspend和dispatch_resume

dispatch_suspend(queue)//暫停某個隊列
dispatch_resume(queue)//恢復某個隊列
這些函數不會影響到隊列中已經執行的任務,隊列暫停後,已經添加到隊列中但還沒有執行的任務不會執行,直到隊列被恢復。

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