Semaphore in Swift

Giới thiệu

Trong lập trình nói chung và iOS nói riêng, việc quản lý đa luồng (multithreads) là một vấn đề phức tạp ngay cả với những lập trình viên kinh nghiệm. Hiện nay đã có nhiều thư viện hỗ trợ lập trình viên dùng multithreads dễ dàng hơn như PromiseKit, RxSwift.... Tuy nhiên, theo quan điểm cá nhân tôi, hiểu bản chất về GCD vẫn có những ưu điểm nhất định. Trong khuôn khổ bài viết này, tôi sẽ đề cập đến semaphore, một khái niệm rất quen trong nhiều ngôn ngữ lập trình, và cùng tìm hiểu semaphore trong Swift có gì khác và cách sử dụng nó thật hiệu quả

Khái niệm:

Semaphore - dùng để giới hạn số lượng tài nguyên tại một thời điểm. Để hiểu về semaphore, chúng ta cần biết những khái niệm sau:

Khởi tạo:

Để khởi tạo semaphore trong Swift 2:

let semaphore = dispatch_semaphore_create(n) 

với n là giá trị ban đầu của semaphore, số tài nguyên lớn nhất có thể đi qua tại một thời điểm. Bạn hãy tưởng tưởng semaphore có một biến Int bên trong nó, ban đầu bạn gán cho nó một giá trị, sau đó có thể tăng hoặc giảm tuỳ nhu cầu.

Tăng:

Sau khi đã khởi tạo, bạn có thể tăng semaphore bằng cú pháp:

dispatch_semaphore_signal(semaphore: dispatch_semaphore_t)

Giảm:

Giảm semaphore bằng cú pháp sau:

dispatch_semaphore_wait(semaphore: dispatch_semaphore_t)

Cách dùng:

Để dùng semaphore chính xác là chỉ có 3 câu lênh khởi tạo, tăng và giảm ở phần trên. Tuy nhiên, sức mạnh thật sự của semaphore chỉ đến khi được dùng với multithreads

Như chúng ta đã biết, khi nhiều thread cùng thay đổi một database tại một thời điểm sẽ xảy ra hiện tượng race condition, khiến chương trình bị crash. Để khắc phục, ta cần dùng semaphore để đảm bảo luôn chỉ có 1 thread thay đổi data tại một thời điểm như sau:

var array = [Int]()
for i in 1...1000 {
    dispatch_async(concurrentQueue) {
        array.append[i]
    }
}

Với vòng for trên, mỗi vòng lặp sẽ thêm một giá trị vào mảng. Tuy nhiên vì nó được thực hiện concurrent nên rất có thể hành động append một phần tử vào mảng sẽ được thực hiện nhiều lần tại cùng một thời điểm, nguyên nhân của race condition và gây crash. Để khắc phục, chúng ta dùng semaphore:

let  semaphore = dispatch_semaphore_creaet(1)// Khởi tạo ban đầu với bộ đếm là 1, mặc định ngay từ đầu chỉ có tối đa 1 luồng được thay đổi data=
var array = [Int]()
for i in 1...1000 {
    dispatch_async(concurrentQueue) {
        dispatch_semaphore_wait(semaphore)
        array.append[i]
        dispatch_semaphore_signal(semaphore)
    }
}

Ở đây, chúng ta tạo một semaphore với giá trị khởi tạo là 1, tức là chỉ có tối đa một thread được thay đổi data tại một thời điểm. Trong vòng for, chúng ta gọi dispatch_semaphore_wait, khi đó bộ đếm giảm từ 1 còn 0, đảm bảo câu lệnh append bên dưới chỉ được một luồng truy cập, khi thực hiện xong, ta gọi dispatch_semaphore_signal để những task đang bị pending trên các thread khác được giải phóng và tiếp tục thực hiện lần lượt.

Kết luận

Trong bài viết chỉ nêu một ứng dụng của semaphore nhưng đã giúp các bạn có cái nhìn tổng quát cũng như cách dùng, hy vọng, bài viết này sẽ giúp ích cho các bạn trong việc xử lý multithreads một cách an toàn và hiệu quả.