Grand Central Dispatch

1. Giới thiệu

Grand Central Dispatch (GCD) là một công nghệ mới được Apple giới thiệu từ Mac OS X Leopard với mục đích giúp cho việc lập trình đa luồng được dễ dàng và hiệu quả hơn.

Với sự ra đời của GCD chúng ta không cần phải tự viết các dòng code để quản lý thread, thay vào đó công việc này sẽ được đẩy xuống cho hệ thống xử lý. Việc duy nhất chúng ta phải làm là định nghĩa các task và thêm chúng vào các dispatch queue thích hợp. GCD sẽ tạo các thread cần thiết và đặt lịch để chạy task. Điều này sẽ làm cho việc quản lý thread hiệu quả hơn.

Dưới đây là 1 ví dụ hay gặp về việc tạo 1 tiến trình xử lý ở background sau đó cập nhật kết quả lên giao diện chương trình ở main thread:

let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
    // do some task
    dispatch_async(dispatch_get_main_queue()) {
        // update some UI

    }
}

2. Dispatch Queue

Dispatch queue được định nghĩa là 1 hàng đợi để lưu các task sẽ được thực thi. Task sau đó sẽ được xử lý theo thứ tự FIFO tức là vào trước sẽ được thực hiện trước.

Dispatch Queue

Có 2 loại dispatch queue:

  • Serial: đợi khi task hiện tại được thực hiện xong mới bắt đầu thực hiện task tiếp theo
  • Concurrent: không cần đợi task hiện tại thực hiện xong

Dispatch Queue Type

Khởi tạo dispatch queue

dispatch_queue_create

Ta có thể tạo một dispatch queue mới bằng hàm dispatch_queue_create:

let serialDispatchQueue = dispatch_queue_create("com.example.test", nil)

Khi các serial dispatch queue được tạo và được thêm task vào đó, hệ thống sẽ tạo một thread cho mỗi queue. Nên việc tạo quá nhiều serial dispatch queue có thể làm chậm hệ thống. Do đó chúng ta chỉ nên dùng serial dispatch queue khi muốn tránh việc cập nhật cùng một dữ liệu trên nhiều thread, các trường hợp còn lại chúng ta nên dùng concurrent dispatch queue.

Việc tạo concurrent dispatch queue được thực hiện như sau:

let concurrentDispatchQueue = dispatch_queue_create("com.example.test", DISPATCH_QUEUE_CONCURRENT)

Main Dispatch Queue/Global Dispatch Queue

Một cách khác để tạo dispatch queue là sử dụng các dispatch queue được cung cấp bởi hệ thống, đó là main dispatch queue và global dispatch queue.

Main dispatch queue là serial dispatch queue, chỉ thực hiện các task lần lượt trên main thread. Các task trên main thread thường là các task thực hiện việc cập nhật giao diện. Việc này cũng giống như việc ta gọi hàm performSelectorOnMainThread của các đối tượng NSObject.

let mainDispatchQueue = dispatch_get_main_queue()

Global dispatch queue là các concurrent dispatch queue được hệ thống cung cấp. Có 4 loại global dispatch queue khác nhau về mức ưu tiên là high, default, low và background. XNU kernel sẽ quản lý các thread cho global dispatch queue và các thread này sẽ có mức ưu tiên tương ứng với queue của chúng.

Ví dụ về việc lấy global dispatch queue:

let globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
let globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
let globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)

Sử dụng dispatch queue

dispatch_set_target_queue

dispatch_set_target_queue thường được dùng để thay đổi mức ưu tiên của queue mới tạo. Khi một queue được tạo bởi hàm dispatch_queue_create, mức ưu tiên mặc định của nó là default, để thay đổi ta có thể làm như sau:

let serialDispatchQueue = dispatch_queue_create("com.example.test", nil)
let globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
dispatch_set_target_queue(serialDispatchQueue, globalDispatchQueueBackground)

dispatch_after

dispatch_after được sử dụng để đặt thời gian bắt đầu task trong queue.

let time = dispatch_time(DISPATCH_TIME_NOW, 3 * Int64(NSEC_PER_SEC))

dispatch_after(time, dispatch_get_main_queue()) {
    // do something
}

Sau khoảng thời gian định trước, task sẽ được thêm vào dispatch queue.

Dispatch Group

Dispatch group được sử dụng để tạo nhóm queue. Thực tế bạn có thể muốn chạy 1 task để thực hiện việc hoàn tất sau khi tất cả các task trong một queue được thực hiện xong. Đoạn code dưới đây sẽ tạo 3 task ở global dispatch queue, sau khi 3 task hoàn tất sẽ chạy 1 task kết thúc ở main dispatch queue.

let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()
dispatch_group_async(group, queue) {
    print("task 1")
}
dispatch_group_async(group, queue) {
    print("task 2")
}
dispatch_group_async(group, queue) {
    print("task 3")
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
    print("done")
}

Ngoài ra chúng ta có thể sử dụng dispatch_group_wait thay thế cho dispatch_group_notify để chờ các task chạy xong:

let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()
dispatch_group_async(group, queue) {
    print("task 1")
}
dispatch_group_async(group, queue) {
    print("task 2")
}
dispatch_group_async(group, queue) {
    print("task 3")
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

Chúng ta có thể đợi "forever" như trên hoặc sau 1 khoảng thời gian:

let result = dispatch_group_wait(group, time)
if result == 0 {
    // all task done
} else {
   // some tasks are still running
}

Khi hàm dispatch_group_wait được gọi, thread gọi hàm sẽ dừng lại cho đến khi hết thời gian chờ hoặc cho đến khi tất cả các task trong group đã hoàn thành.

Sử dụng DISPATCH_TIME_NOW để kiểm tra các task trong group đã kết thúc hay chưa:

let result = dispatch_group_wait(group, DISPATCH_TIME_NOW)

dispatch_barrier_async

dispatch_barrier_async sử dụng để đợi các task khác trong concurrent queue:

let queue = dispatch_queue_create("com.example.test", DISPATCH_QUEUE_CONCURRENT)

dispatch_async(queue) { print("task 1") }
dispatch_async(queue) { print("task 2") }
dispatch_async(queue) { print("task 3") }
dispatch_barrier_sync(queue) {
    print("waiting then do something")
}
dispatch_async(queue) { print("task 4") }
dispatch_async(queue) { print("task 5") }
dispatch_async(queue) { print("task 6") }

Trong ví dụ trên, với việc sử dụng dispatch_barrier_async chúng ta có thể thêm task vào concurrent dispatch queue tại thời điểm tất cả các task cần thực hiện trước kết thúc. Sau khi task được thêm vào bằng lệnh dispatch_barrier_async hoàn thành, các task còn lại sẽ được tiếp tục thực hiện như bình thường.

Dispatch Barrier

dispatch_sync

dispatch_sync sử dụng giống như dispatch_async tuy nhiên nó sẽ đợi task trong queue hoàn thành. Ở đây, "đợi" nghĩa là dừng thread hiện tại. Ví dụ, ta có thể sử dụng kết quả của task thực hiện ở global dispatch queue trong main dispatch queue như dưới đây:

let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_sync(queue, {
    // task
})

Chú ý khi sử dụng dispatch_async vì có thể dẫn tới "deadlock" như ví dụ sau:

let queue = dispatch_get_main_queue()
dispatch_sync(queue, {
    // do something
})

Ở ví dụ trên dispatch_sync sẽ tạo 1 task trong main dispatch queue và chờ nó thực hiện xong. Tuy nhiên do dispatch_sync chạy trong main thread và "chờ" (tức là tạm dừng main thread) nên task sẽ không bao giờ được gọi.

dispatch_apply

dispatch_apply sử dụng để thêm một số lượng task vào dispatch queue và đợi cho đến khi tất cả cả các task hoàn thành:

let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_apply(5, queue) { (index) in
    print(index)
}
print("done")

dispatch_suspend/dispatch_resume

Sử dụng để ngừng/tiếp tục một dispatch queue:

dispatch_suspend(queue)
dispatch_resume(queue)

dispatch_once

dispatch_once được sử dụng để đảm bảo 1 task chỉ được thực hiện 1 lần khi chương trình chạy. Ví dụ dưới đây là implement của Singleton design pattern, việc sử dụng dispatch_once sẽ đảm bảo chỉ một instance của class Singleton được tạo ngay cả trong multi thread:

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: Singleton? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = Singleton()
        }
        return Static.instance!
    }
}

Tham khảo "Pro Multithreading and Memory Management for iOS and OS X"