Grand Central Dispatch (Part 2)

by Hoang Anh Tuan
878 views

Nội dung bài viết:

  • Dispatch Group
  • Semaphores

Dispatch Group (cont):

Ở phần trước, bạn đã biết về cơ chế enter-leave-notify của DispatchGroup để nhận biết khi các task trong group đã hoàn thành.
Vậy nếu bạn muốn block luồng hiện tại cho đến khi các tasks trong group hoàn thành hoặc timeout thì sao? Ngoài cách sử dụng 1 vài logic, thì cách đơn giản hơn là dùng phương thức wait do DispatchGroup cung cấp.

Phương thức wait của DispatchGroup là 1 sync function nên nó có tác dụng block luồng hiện tại cho đến khi mọi tasks trong group đã được thực hiện xong, hoặc timeout.

let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)

var formatter: DateFormatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"

// 1
print("Start task 1 at time: \(formatter.string(from: Date()))")
queue.async(group: group) {
    Thread.sleep(until: Date().addingTimeInterval(10))
    print("End task 1 at time: \(formatter.string(from: Date()))")
}

// 2
print("Start task 2 at time: \(formatter.string(from: Date()))")
queue.async(group: group) {
    Thread.sleep(until: Date().addingTimeInterval(2))
    print("End task 2 at time: \(formatter.string(from: Date()))")
}

// 3
print("Start call time out at time: \(formatter.string(from: Date()))")
if group.wait(timeout: .now() + 5) == .timedOut {
    print("Time out at time: \(formatter.string(from: Date()))")
} else {
    print("All the jobs have completed at time: \(formatter.string(from: Date()))")
}

// 4
queue.async(group: group) {
    print("Try to do task 3 at time: \(formatter.string(from: Date()))")
}
  1. Bạn khởi tạo task đầu tiên, đưa vào queue để thực hiện theo cách async.
  2. Bạn khởi tạo task thứ 2, đưa vào queue để thực hiện theo cách async.
  3. Bạn khai báo hàm wait() với timeOut = 5s. Hàm sẽ block luồng hiện tại cho đến khi các task thực hiện xong hoặc sau 5s.
  4. Khởi tạo 1 task thứ 3, đưa vào queue để thực hiện theo cách async
Mang đoạn code vào playground, thử sửa thời gian để hoàn thành task 1 thành 1s, xem điều gì sẽ xảy ra 😉
  • Sau 2s kể từ lúc bắt đầu, task 2 hoàn thành
  • Khi gọi wait() thì luồng hiện tại sẽ bị block -> Không thực hiện được tiếp task thứ 3 trong khoảng thời gian đó.
  • Sau 5s kể từ lúc Start time out, các task trong group chưa hoàn thành hết do task 1 cần 10s để thực hiện, vì vậy nên dispatchGroup sẽ trả quyền điều khiển về cho luồng.
  • Ngay sau khi luồng được trả quyền điều khiển, luồng thực hiện ngay lập tức task 3.
  • Sau 10s kể từ lúc bắt đầu, task 1 hoàn thành.

Note: Vì wait() block luồng gọi nó, nên bạn không bao giờ nên gọi wait ở main queue.

Semaphores

  • Giả sử bạn có nhiều task cùng truy cập vào 1 tài nguyên chung, và bạn muốn giới hạn số lượng task truy cập vào tài nguyên chung đó tại mỗi thời điểm. -> GCD cung cấp cho bạn Semaphore để giúp bạn xử lí việc này dễ dàng.
  • semaphore cung cấp 2 phương thức là wait()signal(). Chúng tương tự như enter() và leave() của dispatchGroup. wait() sẽ gọi khi bắt đầu 1 task và gọi signal() để thông báo rằng task đã hoàn thành.

Giả sử bạn có 10 task cùng thực hiện 1 lúc, nhưng bạn muốn chỉ có tối đa 4 task chạy tại 1 thời điểm( ví dụ như load ảnh) -> Semaphore sẽ giúp bạn

let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)

// 1
let semaphore = DispatchSemaphore(value: 4)

// 2
for i in 1...10 {
    // 3
    queue.async(group: group) {
        // 4
        semaphore.wait()
        // 5
        defer {
            semaphore.signal()
        }
        print(" Start task \(i) at time: \(Date().timeIntervalSince1970)")

        Thread.sleep(forTimeInterval: 3)
        print("Finish task \(i) at time: \(Date().timeIntervalSince1970)")
    }
}
  1. Khởi tạo 1 semaphore, giá trị value biểu thị cho số lượng task tối đa chạy trong 1 thời điểm.
  2. Tạo 1 vòng lặp gồm 10 task chạy.
  3. Mỗi task sẽ chạy theo kiểu async.
  4. gọi hàm wait() để thông báo là task bắt đầu chạy.
  5. gọi signal() để thông báo là task đã hoàn thành.
Mang đoạn code vào playground, thử không gọi signal() hoặc wait() xem điều gì sẽ xảy ra
  • Task 1, 2, 3, 4 bắt đầu được chạy đầu tiên.
  • Khi task 2, 3, 1 hoàn thành, task 5, 6, 7 ngay lập tức được thực hiện.
  • Task 4 hoàn thành, task 8 ngay lập tức được thực hiện.

    -> Có thể thấy, tại mỗi thời điểm chỉ có tối đa 4 task chạy.

Note:

  • Luôn nhớ gọi hàm signal() khi task được thực hiện xong, nếu không thì semaphore sẽ không biết task đã hoàn thành để thực hiện task mới.
  • Gọi wait() khi bắt đầu 1 task để thông báo cho semaphore rằng 1 task mới sẽ bắt đầu thực hiện.

Ở bài viết tiếp theo, mình sẽ giới thiệu về DispatchBarrier, DispatchWorkItem và Thread Sanitizer.

Nguồn tham khảo: Ray Wenderlich & Lets Build that app.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.