Grand Central Dispatch
Bài đăng này đã không được cập nhật trong 3 năm
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.
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
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_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"
All rights reserved