對GCD的一些理解和實踐,GCD理解實踐

來源:互聯網
上載者:User

對GCD的一些理解和實踐,GCD理解實踐
GCD

  GCD,全程Grand Central Dispatch,是蘋果為了多核並行提出的解決方案。它是使用C語言實現,但是由於用了block來處理回調,所以使用起來十分方便。並且GCD會自動管理線程的生命週期,不需要我們去管理。

任務和隊列

  GCD中有兩個重要的概念,任務和隊列。

  1、任務,就是我們想要處理的事情,任務可以分為同步執行和非同步執行:

  同步(sync):使用dispatch_sync(dispatch_queue_t queue, dispatch_block_t block) 建立,同步執行任務時會阻塞當前線程等待block執行完畢返回。然後當前線程才會繼續執行下去。

  非同步(async):使用dispatch_async(dispatch_queue_t queue, dispatch_block_t block)建立,非同步任務不會阻塞當前線程,任務建立後立即返回線程往下執行。

  2、隊列,存放任務,並將任務由先入先出地派發出去。分為串列隊列和並行隊列:

  串列隊列(Serial queue):隊列中的任務根據建立順序,先入先出地執行,等待上一個任務執行完畢後,才會執行下一個任務,有嚴格的執行先後順序。

  並行隊列(Concurrent queue):隊列會根據先後順序,將任務派發出去,並存執行。所有的任務幾乎都是一起執行的。不過需要注意,GCD 會根據系統資源控制並行的數量,所以如果任務很多,它並不會讓所有任務同時執行。有一個表,可以大體說明任務和隊列之間的配合使用:

  串列隊列 並行隊列
同步執行任務 當前線程,一個一個執行 當前線程,一個一個執行
非同步執行任務 另開線程,一個一個執行 開很多線程,一起執行

  下面是任務和隊列的使用示範:

  

/**     *  串列隊列中的任務會等待正在執行的任務執行結束,排隊執行     */    dispatch_queue_t serial_queue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);    //主隊列    dispatch_queue_t mainQueue = dispatch_get_main_queue();        /**     *  並行,不等待正在執行的任務的處理結果,可以並發執行多個任務     */    dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);        //全域隊列    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);        //在串列隊列中建立幾個同步任務,在主線程順序執行    dispatch_sync(serial_queue, ^{        NSLog(@"task a ,thread:%@",[NSThread currentThread]);    });    dispatch_sync(serial_queue, ^{        NSLog(@"task b ,thread:%@",[NSThread currentThread]);    });    dispatch_sync(serial_queue, ^{        NSLog(@"task c ,thread:%@",[NSThread currentThread]);    });        //在串列隊列中建立幾個非同步任務,在主線程順序執行    dispatch_sync(serial_queue, ^{        NSLog(@"task aa ,thread:%@",[NSThread currentThread]);    });    dispatch_sync(serial_queue, ^{        NSLog(@"task bb ,thread:%@",[NSThread currentThread]);    });    dispatch_sync(serial_queue, ^{        NSLog(@"task cc ,thread:%@",[NSThread currentThread]);    });        //在串列隊列中建立幾個非同步任務,另開1個線程順序執行    dispatch_async(serial_queue, ^{        NSLog(@"task 1 ,thread:%@",[NSThread currentThread]);    });    dispatch_async(serial_queue, ^{        NSLog(@"task 1 ,thread:%@",[NSThread currentThread]);    });    dispatch_async(serial_queue, ^{        NSLog(@"task 1 ,thread:%@",[NSThread currentThread]);    });        //在並隊列中建立幾個非同步任務,另開多個線程同時執行    dispatch_async(concurrent_queue, ^{        NSLog(@"task 11 ,thread:%@",[NSThread currentThread]);    });    dispatch_async(concurrent_queue, ^{        NSLog(@"task 22 ,thread:%@",[NSThread currentThread]);    });    dispatch_async(concurrent_queue, ^{        NSLog(@"task 33 ,thread:%@",[NSThread currentThread]);    });
 工作群組和柵欄

  有時候當我們想要為多個任務添加依賴關係的時候,就可以使用工作群組dispatch_group和dispatch_barrier。

  工作群組是將若干個任務放在一個group 中,這些任務可以在同一隊列也可以在不同隊列,然後用dispatch_group_notify()和dispatch_group_wait()對工作群組中任務的完成進行處理。

1、dispatch_group_notify()中的任務會在group中多有任務執行完畢後執行

dispatch_group_t group = dispatch_group_create();        /**     *  group中所有任務任務執行完畢後,執行dispatch_group_notify中的任務     */    dispatch_group_async(group, serial_queue, ^{        sleep(2);        NSLog(@"serial_queue1");    });    dispatch_group_async(group, serial_queue, ^{        sleep(2);        NSLog(@"serial_queue2");    });    dispatch_group_async(group, concurrent_queue, ^{        sleep(2);        NSLog(@"concurrent_queue1");    });        dispatch_group_notify(group, dispatch_get_main_queue(), ^{        NSLog(@"return main queue");    });

 

2、dispatch_group_wait()中可傳入一個給定時間,如果在等待時間結束前group所有任務執行完畢則返回0,否則返回非0,這個函數是一個同步任務

    /**     *  dispatch_group_wait給定一個時間,如果在等待時間結束前group所有任務執行完畢則返回0,否則返回非0,這個函數是一個同步任務     */    dispatch_group_async(group, serial_queue, ^{        sleep(3);        NSLog(@"serial_queue1");    });    dispatch_group_async(group, serial_queue, ^{        sleep(2);        NSLog(@"serial_queue2");    });    dispatch_group_async(group, concurrent_queue, ^{        sleep(3);        NSLog(@"concurrent_queue1");    });    long i = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 6));    NSLog(@"-- %ld --",i);    dispatch_group_async(group, concurrent_queue, ^{        NSLog(@"finish all");    });

 

3、可以使用dispatch_group_enter()和dispatch_group_leave()來新增工作組的任務,兩者必須要成對出現,兩個函數之間的代碼便是要加入任務住的任務

/**     *  使用dispatch_group_enter和dispatch_group_leave添加組任務,兩者必須要成對出現     */    dispatch_group_enter(group);    sleep(2);    NSLog(@"1");    dispatch_group_leave(group);        dispatch_group_enter(group);    dispatch_async(concurrent_queue, ^{        sleep(3);        NSLog(@"2");        dispatch_group_leave(group);    });        dispatch_group_enter(group);    sleep(2);    NSLog(@"3");    dispatch_group_leave(group);        long i = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 6));    NSLog(@"-- %ld --",i);    dispatch_group_async(group, concurrent_queue, ^{        NSLog(@"finish all");    });

 

  柵欄是將一個隊列中的任務分割成兩部分,在柵欄任務之前添加的任務全部執行完畢後,單獨執行柵欄任務,執行完畢後,再繼續執行後面的任務。柵欄必須單獨執行,不能與其他任務並發執行,因此,柵欄只對並發隊列有意義。柵欄只有等待當前隊列所有並發任務都執行完畢後,才會單獨執行,待其執行完畢,再按照正常的方式繼續向下執行。

dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);        dispatch_async(concurrent_queue, ^{        sleep(2);        NSLog(@"1");    });    dispatch_async(concurrent_queue, ^{        sleep(2);        NSLog(@"2");    });    dispatch_async(concurrent_queue, ^{        sleep(2);        NSLog(@"3");    });        dispatch_barrier_async(concurrent_queue, ^{        sleep(2);        NSLog(@"barrier");    });    dispatch_async(concurrent_queue, ^{        sleep(2);        NSLog(@"finish1");    });        dispatch_async(concurrent_queue, ^{        NSLog(@"finish2");    });

 

重複執行dispatchApply 和 單次執行dispatch_once

  dispatch_apply()是將一個任務提交到隊列中重複執行,並行或者串列由隊列決定,dispatch_apply會阻塞當前線程,等到所有任務完成後返回

dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);    dispatch_apply(5, concurrent_queue, ^(size_t index) {        sleep(1);        NSLog(@"index:%zu",index);    });    NSLog(@"finish");

  dispatch_once()確保block內代碼在整個應用運行期間只執行一次

//確保block內代碼在整個應用運行期間只執行一次    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        NSLog(@"just run once in application");    });
線程同步、訊號量和線程死結  

  多執行緒經常需要考慮到資源搶佔的問題,比如經典的購票問題,寫資料庫等問題。處理問題的辦法有很多,下面介紹最簡單的兩種做法:加上同步鎖,或者用訊號量來解決。

  同步鎖,每當有線程訪問鎖裡的資源時,會將此部分鎖住,拒絕其他線程訪問。直到佔用的線程推出後才解鎖,允許其他資源訪問。

//同步鎖,對block內的代碼加鎖,同一時間內只允許一個線程訪問    @synchronized(self) {        NSLog(@"lock");    };

  訊號量dispatch_semaphore,一開始設定訊號的總量,然後用dispatch_semaphore_wait()和dispatch_semaphore_signal()來管理訊號量,達到控制線程訪問的目的

//訊號量dispatch_semaphore    dispatch_group_t group = dispatch_group_create();        //設定總訊號量    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);    for (int i = 0; i<100; i++) {        //設定等待訊號,如果此時訊號量大於0,那麼訊號量減一併繼續往下執行        //如果此時訊號量小於0,會一直等待,直到逾時        //如果逾時返回非零,成功執行返回0        dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*50));                dispatch_group_async(group, concurrent_queue, ^{            sleep(1);            NSLog(@"%d",i);            //發送訊號,讓訊號量加一            dispatch_semaphore_signal(semaphore);        });    }        dispatch_group_notify(group, concurrent_queue, ^{        NSLog(@"finish");    });

  

  GCD儘管使用起來非常方便,但是如果使用不當也活造成一些麻煩,下面列舉幾個會造成線程死結的場合:

//在並行隊列中,在當前隊列調用dispatch_sync,並傳入當前隊列執行,並不會造成deadlock。dispatch_sync會阻塞當前線程,但是由於隊列是並存執行,所以block中的任務會馬上執行後返回。- (void)syncAndConcurrentQueue {    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);    dispatch_sync(queue, ^{                NSLog(@"Jump to concurrent.queue! ,thread:%@",[NSThread currentThread]);        dispatch_sync(queue, ^{            sleep(3);            NSLog(@"success6 ,thread:%@",[NSThread currentThread]);        });         NSLog(@"return");    });}//在串列隊列中,在當前隊列調用dispatch_sync,並傳入當前隊列執行,會造成deadlock。 dispatch_sync會阻塞當前線程,等待block中的任務執行完之後再繼續執行,但是由於隊列是串列執行,block中的任務放在最後,所以永遠沒有機會執行,線程死結- (void)synAndSerialQueue {        dispatch_queue_t queue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);    dispatch_async(queue, ^{        NSLog(@"Jump to serial.queue!");                dispatch_sync(queue, ^{            NSLog(@"success");        });        NSLog(@"return");    });}//任務1會阻塞主線程,直到block中執行完畢返回,任務二在主線程添加了了一個同步任務,阻塞當前線程,知道任務執行完畢返回,而任務2沒有機會被執行。造成兩條線程死結。- (void)recycle {        dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);        //任務1    dispatch_sync(concurrent_queue, ^{                NSLog(@"jump to concurrent queue");                //任務2        dispatch_sync(dispatch_get_main_queue(), ^{                        NSLog(@"return main queue");        });            });}

 

 

總結

  以上是使用GCD的一些心得,GCD使用起來儘管十分便利,但是在處理一些情境比如取消任務時候會很麻煩,所以實現簡易功能的時候推薦使用GCD,如果功能複雜的話建議使用NSOperation和NSOperationQueue,NSOperationQueue的底層也是由GCD實現的,完全物件導向,所以使用起來更好理解。下次有空講講NSOperation和NSOperationQueue。以上代碼demo地址:https://github.com/GarenChen/GCDDemo

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.