深入理解Thread線程和Queue隊列

思考一段代碼

我們先來看一段代碼,猜猜一下代碼的的運行結果:

    // 主隊列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    // 給主隊列設置一個標記
    dispatch_queue_set_specific(mainQueue, "key", "main", NULL);

    // 定義一個block任務
    dispatch_block_t log = ^{
        // 判斷是否是主線程
        NSLog(@"main thread: %d", [NSThread isMainThread]);
        // 判斷是否是主隊列
        void *value = dispatch_get_specific("key");
        NSLog(@"main queue: %d", value != NULL);
    };

    // 全局隊列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    // 異步加入全局隊列里
    dispatch_async(globalQueue, ^{
        // 異步加入主隊列里
        dispatch_async(dispatch_get_main_queue(), log);
    });

    NSLog(@"before dispatch_main");
    dispatch_main();
    NSLog(@"after dispatch_main");

運行結果:

2018-05-08 15:08:05.557398+0800 TestRunLoop[28206:767410] before dispatch_main
2018-05-08 15:08:05.557682+0800 TestRunLoop[28206:767462] main thread: 0  //不是主線程
2018-05-08 15:08:05.557814+0800 TestRunLoop[28206:767462] main queue: 1 //是主隊列

什么情況?派發給主隊列的任務不是在主線程上運行,跟我們平常用的和理解的完全不一樣。

不要激動,導致這種原因最關鍵的是這行代碼dispatch_main() ,就是這貨讓主隊列的任務在非主線程運行。

這個方法蘋果官方文檔這樣解釋的:

/*!
 * @function dispatch_main
 *
 * @abstract
 * Execute blocks submitted to the main queue.
 * 執行提交給主隊列的任務blocks
 *
 * @discussion
 * This function "parks" the main thread and waits for blocks to be submitted
 * 
 * to the main queue. This function never returns.
 * 這個函數會阻塞主線程并且等待提交給主隊列的任務blocks完成,這個函數永遠不會返回
 * 
 * Applications that call NSApplicationMain() or CFRunLoopRun() on the
 * main thread do not need to call dispatch_main().
 *
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NOTHROW DISPATCH_NORETURN
void
dispatch_main(void);

意思是這個方法會阻塞主線程,然后在其它線程中執行主隊列中的任務,這個方法永遠不會返回(意思會卡住主線程)

如果去掉dispatch_main()這行代碼,就會正常在主線程里執行任務

2018-05-08 15:20:16.358742+0800 TestRunLoop[28367:779939] main thread: 1 //主線程
2018-05-08 15:20:16.359066+0800 TestRunLoop[28367:779939] main queue: 1 //主隊列

所以在主隊列的任務通常是在主線程里執行,但是不一定,我們可以主動去執行被添加到主隊列MainQueue的任務task(也就是說我們可以主動來調用添加到主線程隊列的blocks)。可以使用以下任一個來實現:dispatch_main()UIApplicationMain()CFRunLoopRun()


那我們再思考一下,主線程是否可以運行非主隊列的任務blocks嗎?答案是可以的,比如下面的代碼:

    // 同步加入全局隊列里
    dispatch_sync(globalQueue, ^{
        // 判斷是否是主線程
        NSLog(@"main thread: %d", [NSThread isMainThread]);
        // 判斷是否是主隊列
        void *value = dispatch_get_specific("key");
        NSLog(@"main queue: %d", value != NULL);
    });

執行結果:

2018-05-08 15:27:31.215279+0800 TestRunLoop[28442:785851] main thread: 1 // 主線程
2018-05-08 15:27:33.519456+0800 TestRunLoop[28442:785851] main queue: 0 // 全局隊列

所以通過dispatch_sync()執行的block不會開辟新的線程,而是在當前的線程(即主線程)中同步執行block

runloop和queue的區別

  • runloop和queue的區別

runloopqueue各自維護著自己的一個任務隊列,在runloop的每個周期里面,會檢測自身的任務隊列里面是否存在待執行的task并且執行。但主線程的情況比較特殊,在main runloop的每個周期,會去檢測main queue是否存在待執行任務,如果存在,那么copy到自身的任務隊列中執行

  • async的實現不同

在非主線程之外,runloopqueue的任務隊列是互不干擾的,因此兩者處理任務的機制也是完全不同的。當async任務到隊列時,GCD會嘗試尋找一個線程來執行任務。由于串行隊列同時只能與一個線程掛鉤,因此GCD會讓該線程執行完已有任務后,才執行async到隊列中的任務。

多線程的實現有以下幾種方式

線程類型 簡介 語言 線程生命周期 使用頻率
pthread * 一套通用的多線程API
* 適用于Unix\Linux\Windows\OSX等系統
* 跨平臺\可移植
* 使用難度大
C 程序員管理 幾乎不用
NSThread * 使用更加面向對象
* 簡單易用
* 可直接操作線程對象
OC 程序員管理 偶爾使用
GCD * 旨在替代NSThread等線程技術
* 充分利用設備的多核
C 自動管理 經常使用
NSOperation * 基于GCD(底層是GCD)
* 比GCD多了一些更簡單實用的功能
* 使用更加面向對象
OC 自動管理 經常使用

串行與并發

類型 全局并發隊列 手動創建串行隊列 主隊列
同步(sync) * 沒有開啟新線程
* 串行執行任務
* 沒有開啟新線程
* 串行執行任務
* 沒有開啟新線程
* 串行執行任務
異步(async) * 開啟新線程
* 并發執行任務
* 開啟新線程
* 串行執行任務
* 沒有開啟新線程
* 串行執行任務

隊列和線程

gcd-pool.png

參考:

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

推薦閱讀更多精彩內容