1. 程式人生 > >iOS/MacOS多線程編程GCD

iOS/MacOS多線程編程GCD

object 說明 create 相對時間 objc 一個 code ble pen

GCD和Block一起,使得iOS多線程編程變得簡單優雅許多。如此優雅簡單的多線程API真希望C和C++標準中也會有

One of the technologies for starting tasks asynchronously is Grand Central Dispatch (GCD). This technology takes the thread management code you would normally write in your own applications and moves that code down to the system level. All you have to do is define the tasks you want to execute and add them to an appropriate dispatch queue. GCD takes care of creating the needed threads and of scheduling your tasks to run on those threads. Because the thread management is now part of the system, GCD provides a holistic approach to task management and execution, providing better efficiency than traditional threads.

本文相關代碼

遠古時代

- (void) start {
    [self performSelectorInBackground:@selector(doWork) withObject:nil];
}

- (void) doWork {
    @autoreleasepool {
        printf("doing work");
        [self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
    }
}

- (void) doneWork {
    printf("done work");
}

GCD時代

- (void) start {
    dispatch_queue_t queue_for_background_threads = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue_for_background_threads, ^{
        printf("doing work");
        dispatch_async(dispatch_get_main_queue(), ^{
            printf("done work");
        });
    });
}

GCD API

suspend和resume

dispatch_resume開始一個對列,蘋果貌似喜歡用resume表示開始

dispatch_suspend暫停一個隊列

dispatch_queue_create

dispatch_get_main_queue是一個serial queue

dispatch_get_global_queue是一個concurrent queue

// FIFO 每次只執行一個, 不同的Serial Queue是按照並行執行的
dispatch_queue_t serial = dispatch_queue_create("com.mogoal.serial", DISPATCH_QUEUE_SERIAL);
// FIFO 每次可以執行多個
dispatch_queue_t concurrent = dispatch_queue_create("com.mogoal.concurrent", DISPATCH_QUEUE_CONCURRENT);

dispatch_set_target_queue

dispatch_set_target_queu該方法會把代碼運行到目標queue上,使用時註意不要出現循環,如果出現循環,就懵逼了。原來的queue會繼承target queue的優先級。你可以通過獲取dispatch_get_global_queue來設置優先級。??註意不要改變dispatch_get_main_queuedispatch_get_global_queue

如下面的代碼,NSMutableArray是非線程安全的,多個線程同時執行addObject,會導致程序崩潰。我們把serial設置為concurrent的目標隊列,本來concurrent是允許並行執行的,但是目標serial不允許,所以只能夠順序執行。

dispatch_queue_t serial = dispatch_queue_create("com.mogoal.serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrent = dispatch_queue_create("com.mogoal.concurrent", DISPATCH_QUEUE_CONCURRENT);

dispatch_set_target_queue(concurrent, serial);

NSMutableArray *result = [[NSMutableArray alloc] init];
for(int i = 0; i < count; i++){
    [result addObject:@(i)];
}

NSMutableArray *array = [[NSMutableArray alloc] init];

for(int i = 0; i < count + 1; i++){
    dispatch_async(concurrent, ^{[array addObject:@(i)];});
    if (i == count) {
        dispatch_async(concurrent, ^{
            XCTAssertEqual(array, result);
        });
    }
}

dispatch_group

dispatch_group很像一個守望者,守護著所有的任務都安全的知青完畢後,最後執行dispatch_group_notify指定的任務

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(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});

或者你可以用dispatch_group_wait, 返回值==0,則所有任務一執行完畢,若!=0, 說明在指定時間之前,還有未完成任務

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(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"done");

dispatch_barrier_async

對於並行執行,會產生race condiion. 通常我們會用鎖來解決這樣的數據不一致問題。 來看看dispatch_barrier_async怎麽幫助我們解決這個問題。

dispatch_barrier_async提交一個block到並行queue中,並立即返回。提交的block到達隊列前排處不會立即執行。而是等待當前queue中所有任務執行完畢,才去單獨執行。之後的任務只有等待block執行完畢,才會繼續並行下去。

dispatch_barrier_async很像酒館裝修,在裝修任務來時,等待所有客人(任務)離場,然後關門裝修更新,更新完後,才能夠讓其他客人(任務)進來。或者說dispatch_barrier_async臨時將Concurrent Queue轉換Serial Queue, 使任務一次進行

例如下面代碼,即使寫入需要2s,接下來的讀取任務也會等待寫入完畢,才再讀取。

__block int another_num = 200;
dispatch_queue_t low_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

dispatch_suspend(low_queue);

dispatch_async(high_queue, ^{ printf("\nRight case\n"); });
dispatch_async(low_queue, ^{ printf("===%d", another_num); });
dispatch_async(low_queue, ^{ printf("===%d", another_num); });
dispatch_async(low_queue, ^{ printf("===%d", another_num); });
dispatch_async(low_queue, ^{ printf("===%d", another_num); });
dispatch_barrier_sync(low_queue, ^{
    sleep(2);
    printf("===low_write");
    another_num += 1;
    printf("===");
});
dispatch_async(low_queue, ^{ printf("===%d", another_num); });
dispatch_async(low_queue, ^{ printf("===%d", another_num); });
dispatch_async(low_queue, ^{ printf("===%d", another_num); });
dispatch_async(low_queue, ^{ printf("===%d", another_num); });

dispatch_resume(low_queue);

sleep(5);

dispatch_semaphore

前面已經說過,NSMutableArray是非線程安全的,如果再多線程下,程序會崩潰,這裏我們通過信號量來控制addObject的順序執行。

dispatch_semaphore_wait等待, 如果semaphore > 0 ,並且消耗semaphore的值-1dispatch_semaphore_signal釋放,使得semaphore的值+1

int count = 100;
dispatch_queue_t concurrent = dispatch_queue_create("com.mogoal.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t flag = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];

for(int i = 0; i < count; i++){
    dispatch_semaphore_wait(flag, DISPATCH_TIME_FOREVER);
    dispatch_async(concurrent, ^{[array addObject:@(i)];});
    NSLog(@"%d", i);
    dispatch_semaphore_signal(flag);
}

dispatch_group_wait類似,semaphore也有dispatch_semaphore_wait函數

dispatch_apply

dispatch_sync函數類似,dispatch_apply也是一個同步函數。來執行一個N次, 可以用來遍歷

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//數數
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"count %zu", index);
});
NSLog(@"done");

//遍歷數組
int count = 100;

NSMutableArray *array = [[NSMutableArray alloc] init];
for(int i = 0; i < count; i++){
    [array addObject:@(i)];
}

dispatch_apply(count, queue, ^(size_t index) {
    NSLog(@"%@", array[index]);
});

dispatch_once

dispatch_once是執行任務,僅僅一次,好處是線程安全。實現單例模式時候使用較多

dispatch_after

dispatch_after會在一定的時間後,將任務添加到對應的隊列。

dispatch_time是相對時間,dispatch_walltime是絕對時間。

main queue是通過runloop裏執行的,如果runloop每秒調用60次,那麽dispatch_after會有帶蓋1/60s的延遲。也就是這個API在時間上並不是絕對準確。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(001 * NSEC_PER_SEC));

dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"dispatch_after");
});

dispatch_sync

??註意以下dispatch_sync,在一個隊列中, sync執行到當前隊列中會死鎖

dispatch_queue_t queue = dispatch_queue_create("com.mogoal.MySerialDispatchQueue", NULL);

dispatch_async(queue, ^{
    dispatch_sync(queue, ^{
        printf("hello world\n");
    });
});

//TBD dispatch io

iOS/MacOS多線程編程GCD