1. 程式人生 > >關於GCD中序列佇列、併發佇列和同步執行、非同步執行的探討

關於GCD中序列佇列、併發佇列和同步執行、非同步執行的探討

Dispatch Queues

Dispatch queue是一個物件,它可以接收任務,並將任務以先到先執行的順序來執行。Dispatch queue可以使併發的或序列的。併發任務會基於系統負載來合適地併發執行,序列佇列同一時間只執行單一任務。

GCD共有三種佇列型別:

1、mainqueue:通過dispatch_get_main_queue()獲得,這是一個與主執行緒相關的序列佇列。

2、global queue:全域性佇列是併發佇列,由整個程序共享。存在著高、中、低三種優先順序的全域性佇列。呼叫dispath_get_global_queue並傳入優先順序來訪問佇列。

3、使用者佇列:通過函式dispatch

_queue_create建立的佇列,這些佇列是序列的。

對於序列佇列和併發佇列的理解:

序列佇列,一次只執行一個任務,在佇列中採用先入先出的方式從runloop中取出任務


併發佇列,可一次性執行多個任務,同樣也是採用先入先出的方式取出任務,但是利用多執行緒來實現併發:


對於同步執行和非同步執行:同步執行會等待任務結束後再返回,所以同步操作是有序的,它的操作順序就是先進先出準則;非同步執行再把任務放入佇列後將直接返回而不等待務執行完畢,故非同步操作是無序的。

對於同步方法,有一個經典的死鎖案例:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"ahha");
    });
    
    NSLog(@"111");
    self.view.backgroundColor = [UIColor redColor];
    
    // Do any additional setup after loading the view, typically from a nib.
}

這兩NSLog永遠都不會執行,上文已經說到,放入dispatch_main_queue中的任務會被放到主執行緒執行,同步方法sync是講任務放入佇列,然後等待任務完成後才會返回,主隊列當前執行的為viewDidLoad方法,由此就又成了一個互相等待的死鎖,即viewDidLoad方法須等待dispatch_sync這個同步方法執行完後繼續執行,而同步方法又在等待佇列中排在他前面的任務viewDidLoad執行完成..waiting…

總結一下就是主佇列中新增同步操作永遠不會被執行,會死鎖。

相信很多人都被序列佇列、併發佇列和同步、非同步執行的各種組合整的很崩潰。我的建議是,首先一定要先弄清楚序列、併發和同步,非同步各自的真正意思到底是什麼,對於dispatch_asyc(dispatch_main_queue,task)…這樣的語句可以這樣解讀:把任務放入…佇列…執行,再結合這幾個名詞的定義,思索一下就能明白。下面我們分類各自看一下:

1把任務放入序列佇列同步執行

 dispatch_sync(myQueue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    NSLog(@"over");

它的列印結果是這樣的:


我們可以看見,不會建立新執行緒且切操作會順序執行。你可能會疑惑:為什麼同樣都是在主執行緒執行,這樣沒有死鎖。其實這個和執行緒沒有關係,和佇列有關係,只要不放在主佇列就不會阻塞主佇列上的操作(各種系統的UI方法),這個操作只是選擇了合適的時機在主執行緒上跑了一下而已~

2把任務放入序列佇列非同步執行

    dispatch_async(myQueue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    NSLog(@"also over");

列印結果:

對比上面的,操作順序執行,建立了新的執行緒。順序執行是因為佇列是序列佇列,採取的是先入先出的排程演算法。而also over的列印線上程列印之前是因為我們採取的是非同步執行方式,程式在將操作放入佇列後不會等待這個block執行完成而是直接執行下面的程式碼。換一種方式你可能更能理解這一種"順序執行"

for (int i = 1 ; i <= 10; i++) {

dispatch_async(myQueue,^{

NSLog(@"%d___%@",i,[NSThread currentThread]);

       });

   }

列印結果:



我們還可以發現,至始至終操作都是在同一個執行緒上面執行。


3把任務放入併發佇列同步執行

    dispatch_sync(concurrentQueue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    NSLog(@"over");

列印結果:


操作不會建立執行緒,那麼操作是不是順序的呢?我們上面說了,序列佇列去操作是先進先出順序執行的,那併發佇列呢?還是讓程式碼告訴我們吧:

for (int i = 1; i < 10; i++) {
        dispatch_sync(concurrentQueue, ^{
            NSLog(@"%d___%@",i, [NSThread currentThread]);
        });
    }

看一下執行結果:



發現是順序執行的。但是一定要清楚,這種順序執行和操作佇列為併發佇列沒有關係!而是因為這些操作均為同步操作,所以每一個操作放入佇列後都會被等待執行完成才會放入下一操作,造成了這種順序執行的現象。其實併發佇列還是很想不那麼順序的用多執行緒去併發執行的,這就需要非同步操作的配合啦:

4把任務放入併發佇列非同步執行

for (int i = 1; i < 10; i++) {
        dispatch_async(concurrentQueue, ^{
            NSLog(@"%d___%@",i, [NSThread currentThread]);
        });
    }

相信你都可以才出結果了:

顯然,操作是無序的,且建立了不止一個執行緒。

由於全域性佇列和主佇列分別對應的是併發佇列和序列佇列,所以這裡就不再展開討論了。值得注意的是主佇列同步執行是不允許的,會造成死鎖,切記!