Skip to main content

Các khái niệm cơ bản về Grand Central Dispatch (GCD)


Khái niệm về GCD mình sẽ ko nhắc lại nữa. Bài viết hôm nay mình chỉ muốn nêu ra 1 số định nghĩa rõ ràng cho người mới học về GCD. Để nắm rõ về GCD các bạn cần hiểu các khái niệm sau đây:
  1. Serial Queue và Concurrent Queue
  2. Thread
  3. Các hàng đợi của GCD
  4. Sync and Async
  5. Không chạy DispatchQueue.main.sync trên main thead
  6. Các ví dụ về GCD

Hàng đợi là tập hợp các task , chạy trên chính luồng chính hoặc chạy trên nền.
Task theo nghĩa đen là 1 khối code được gán cho hàng đợi và nó có thể được sử dụng nhiều lần(được sử dụng lại). Việc thực hiện các task phải tuân theo quy tắc FIFO(First in, First out) .
Một hàng đợi chứa nhiều task , mỗi task có thể thực thi theo kiểu serial hoặc concurrent. Một chuỗi các task được thực hiện serial , nếu task này được thực hiện xong thì các task khác mới được thưc hiện. Mặt khác 1 chuỗi các task được thực hiện Concurrent nếu nhiều task cùng thực thi cùng 1 lúc.

1. Serial Queue và Concurrent

Serial queue:

  • Thưc hiện các task một cách tuần tư. Một task chỉ được thực hiện khi mà task phía trước nó trong hàng đợi đã thực hiện xong và kết thúc. Thứ tự các task được nhập vào hàng đợi chính cũng chính là thứ tự các task được thực hiện và kết thúc.

Concurrent queue:

  • Các task được thưc hiện 1 cách đồng thời.Khi các task nhập vào hàng đợi thì các task này có thời gian bắt đầu thực hiện gần như bằng nhau , nhưng tuỳ thuộc vào mỗi task mà chúng có thời gian hoàn thành khác nhau.
Note: Mỗi 1 ô vuông được đánh số thứ tự là 1 task. Mỗi 1 task theo nghĩa đen chính là 1 khối block code.

2. Thread là gì

Hiểu đơn giản nó là các luồng để thực hiện các task trong hàng đợi. Vậy serial, concurrent queue có mấy luồng? Serial queue chỉ có 1 luồng duy nhất còn concurrent queue thì có nhiều luồng.
Nhìn trên hình: Nếu queue là concurrent queue và có 2 thread thì chương trình sẽ chia các task trong hàng đợi cho 2 thread 1 và 2 ,để các thread thực hiện các task.

3. Các hàng đợi của GCD

  1. Main queue( nó chính là serial queue nó chạy các task trên main thread): Sử dụng để cập nhật giao diện người dùng sau khi hoàn thành công việc trong 1 task của concurrent queue.
  2. Global queue( là concurrent queue): Sử dụng để thực hiện các công việc không phải giao diện người dùng dưới background.
  3. Customer queue(nó có thể là serrial queue hoặc concurrent queue)

Ví dụ:


4. Sync and Async

  • Sync: Thực thi các tác vụ một cách đồng bộ với hàng đợi hiện tại. Current queue sẽ bị block để task trên different queue thực thị.
Mình sẽ viết 1 ví dụ để các bạn hiểu rõ hơn:
Hàng đợi queue chạy với phương thức sync:
Chương trình sẽ dừng task B đang chaỵ trên main thread để cho task A đang chạy trong hàng đợi queue thực hiện vì nó đang chạy sync. Kết quả:
  • Async: chạy bất đồng bộ với hàng đợi hiện tại, xử lý công việc song song với hàng đợi hiện tại.Nghĩa là chương trình sẽ trả điều khiển về hàng đợi hiên tại ngay lập tức sau khi mà khởi chạy task trên hàng đợi khác mà không chờ task trên hàng đợi hiện tại kết thúc. Nếu chúng ta thay đổi hàng đợi queue chạy với phương thức async: thì chương trình sẽ chaỵ task A trên hàng đợi queue và task B trên main thread cùng 1 lúc:
Kết quả:

5. Không chạy DispatchQueue.main.sync trên main thead!

Khi chúng ta gọi phương thức sync trên main thread thì ứng dụng sẽ bị crash app , vì DispatchQueue.main là 1 serial queue nó chỉ có duy nhất 1 thread . Khi gọi phương thức sync thì chương trình sẽ block luồng chính, luồng chính sẽ đợi các task thực hiện xong nhưng các task sẽ không bao giờ hoàn thành vì nó sẽ không thể bắt đầu do hàng đợi bị block ⇒ hiện tượng deadlock. Mình sẽ nêu ra 1 ví dụ về deadlock:
In Apple docs, it says: Important: You should never call the dispatch_sync or dispatch_sync_f function from a task that is executing in the same queue that you are planning to pass to the function. This is particularly important for serial queues, which are guaranteed to deadlock, but should also be avoided for concurrent queues.
“Important: Bạn không bao call dispathch_sync từ một task đang thực hiện trong cùng một hàng đợi. Điều này quan trong đặc biệt quan trọng đối với serial queue vì dẫn đến hiện tượng deadlock nhưng cũng nên tránh đối với concurrent queue. ”

6. Các ví dụ về GCD

Mình sẽ nêu ra 1 số ví dụ các bạn hãy tự đoán kết quả để hiểu hơn về GCD nhé:
VD1: Hỏi: Khi mình tạo 1 concurrent queue chạy với phương thức async thì nó sẽ in ra lần lượt các giá trị của i hay in ra 1 cách lộn xộn ?
Trả lời: Tuy đây là concurrentQueue có nhiều thread để chạy các task song song cho thực hiện với phương thức async nhưng các giá trị i vẫn in ra lần lượt đơn giản là cả vòng lặp for chỉ là 1 task duy nhất. Câu hỏi này mình đã từng bị các anh phỏng vấn hỏi, đây chỉ là câu hỏi mẹo 😄.
VD2: Hỏi : khi chạy như code bên dưới nó sẽ in ra như thế nào? Trả lời: Câu này dễ phải không mình có giải thích 1 chút ở phần Sync, các bạn tự trả lời nhé.
VD 3: Hỏi : 2 hàng đợi firstQueue và secondQueue có độ ưu tiên bằng nhau cùng chạy với phương thức sync thì kết quả sẽ in ra như thế nào Kết quả : các bạn tự viết code run nhé Trả lời: Vì chạy với phương thức sync nên hàng đợi secondQueue sẽ bị block để hàng đợi firstQueue thực hiện task trước sau khi task firstQueue thì chương trình mới chạy task secondQueue.
Cũng đoạn code trên nhưng mình thay thành async thì sao: Trả lời : Các bản hãy tự nghĩ và tự giải thích xem, đơn gian mà đúng ko !
VD4: cũng Ví dụ như trên nhưng mình thay đổi độ ưu tiên, Các bạn có bao giờ tự hỏi mình độ ưu tiên của các các hàng đợi nó có ý nghĩa gì
Kết quả: Trả lời: Hàng đợi thứ hai có độ ưu tiên cao hơn hàng đợi thứ nhất, hệ thống sẽ cung cấp tài nguyên cho hàng đợi thứ hai vì nó được đánh dấu là hàng đợi quan trọng . Một khi hàng đợi thứ hai hoàn thành thì hệ thông sẽ cung cấp tài nguyên cho hàng đợi thứ nhất.
Bạn viết tuy còn sơ sài nhưng cũng là 1 tài liệu cho những bạn mới tiếp xúc về GCD hiểu và nắm rõ kiến thức cơ bản để học tiếp các phần DispatchGroup, NSOperation.
**Tài liệu tham khảo: **

Comments

Popular posts from this blog

Swift Tool Belt, Part 1: Adding a Border, Corner Radius, and Shadow to a UIView with Interface Builder

During my iOS work, I’ve assembled a set of code that I bring with me on every iOS project. I’m not talking about large frameworks or CocoaPods here. These are smaller Swift extensions or control overrides that are applicable to many projects. I think of them as my tool belt. In this post, I’ll show you an extension that will add a border, a corner radius, and a shadow to any UIView, UIButton, or UILabel and allow you to preview what it will look like in Interface Builder. Back in 2014, I wrote a blog post on Expanding User-Defined Runtime Attributes in Xcode where I added a border, corner radius, and shadow to a UIView using Interface Builder’s user-defined runtime attributes. This solution had no type checking—you had to type the property you wanted to modify by hand and often had to look up what it was called. You also had to run your project in order to see the effect of the runtime attribute. Starting with Xcode 6 , there is a new mech...

Alamofire vs URLSession

Alamofire vs URLSession: a comparison for networking in Swift Alamofire and URLSession both help you to make network requests in Swift. The URLSession API is part of the foundation framework, whereas Alamofire needs to be added as an external dependency. Many  developers  doubt  whether it’s needed to include an extra dependency on something basic like networking in Swift. In the end, it’s perfectly doable to implement a networking layer with the great URLSession API’s which are available nowadays. This blog post is here to compare both frameworks and to find out when to add Alamofire as an external dependency. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! This shows the real power of Alamofire as the framework makes a lot of things easier. What is Alamofire? Where URLSession...

Frame vs Bounds in iOS

This article is a repost of an answer I wrote on Stack Overflow . Short description frame = a view’s location and size using the parent view’s coordinate system ( important for placing the view in the parent) bounds = a view’s location and size using its own coordinate system (important for placing the view’s content or subviews within itself) Details To help me remember frame , I think of a picture frame on a wall . The picture frame is like the border of a view. I can hang the picture anywhere I want on the wall. In the same way, I can put a view anywhere I want inside a parent view (also called a superview). The parent view is like the wall. The origin of the coordinate system in iOS is the top left. We can put our view at the origin of the superview by setting the view frame’s x-y coordinates to (0, 0), which is like hanging our picture in the very top left corner of the wall. To move it right, increase x, to move it down increase y. To help me remember bound...