DispatchQueue: khái niệm, hàng đợi GCD và kiến thức cơ bản về đa luồng

Tác giả: IT Sectr Đã đăng: 2026-03-16 Thời gian đọc: 8 phút

DispatchQueue là một hàng đợi cơ bản của Grand Central Dispatch (GCD) để quản lý các tác vụ bất đồng bộ trong iOS và macOS. Theo Apple Developer Documentation, 2026, DispatchQueue trừu tượng hóa việc quản lý luồng khỏi nhà phát triển thông qua hàng đợi serial và concurrent. GCD tự động phân phối tác vụ qua nhóm luồng hệ thống, loại bỏ nhu cầu tạo và hủy luồng thủ công.

Những điểm chính

  • DispatchQueue là sự trừu tượng hóa chính của GCD để thực thi mã bất đồng bộ trong iOS
  • Hàng đợi serial thực thi tác vụ theo trình tự nghiêm ngặt, loại bỏ điều kiện cạnh tranh
  • Hàng đợi concurrent chạy nhiều tác vụ song song qua nhóm luồng hệ thống
  • QoS đặt mức ưu tiên cho tác vụ — từ userInteractive đến background
  • DispatchQueue.main là hàng đợi duy nhất để cập nhật UIKit trên luồng chính

DispatchQueue và Grand Central Dispatch là gì

DispatchQueue là một đối tượng của framework Grand Central Dispatch (GCD) quản lý việc thực thi tác vụ trong hàng đợi luồng hệ thống hoặc tùy chỉnh. Grand Central Dispatch là một thư viện cấp thấp của Apple, có sẵn từ iOS 4 và macOS 10.6, hoàn toàn trừu tượng hóa việc quản lý luồng khỏi nhà phát triển. GCD sử dụng nhóm luồng của hệ điều hành và tự động mở rộng số lượng luồng theo tải của thiết bị.

Nhà phát triển không cần tạo và hủy luồng thủ công — GCD đảm nhận nhiệm vụ này, cung cấp API đơn giản qua DispatchQueue. Một tác vụ dưới dạng closure được gửi đến hàng đợi qua các phương thức sync hoặc async. Trong trường hợp đầu tiên, luồng gọi bị chặn cho đến khi tác vụ hoàn thành; trong trường hợp thứ hai, việc thực thi tiếp tục ngay lập tức.

Theo Apple (2026), GCD sử dụng nhóm luồng hệ thống thích ứng với số lượng lõi và tải CPU hiện tại. Hàng đợi concurrent không tạo luồng mới cho mỗi tác vụ — GCD tái sử dụng luồng từ nhóm, giảm thiểu chi phí tạo luồng.

Kiến trúc GCD

Grand Central Dispatch bao gồm ba thành phần chính: hàng đợi (DispatchQueue), nhóm (DispatchGroup) và semaphore (DispatchSemaphore). Hàng đợi là phần tử chính nhận tác vụ dưới dạng khối mã. DispatchGroup đồng bộ hóa việc thực thi nhiều tác vụ, trong khi DispatchSemaphore giới hạn quyền truy cập vào tài nguyên dùng chung đến một số lượng luồng cụ thể.

Mỗi hàng đợi GCD được liên kết với một lớp QoS (Quality of Service) cụ thể, thông báo cho hệ thống về tầm quan trọng của tác vụ. Hệ thống sử dụng QoS để phân phối thời gian CPU giữa các hàng đợi, ưu tiên các tác vụ quan trọng hơn — như cập nhật UI hoặc xử lý chạm của người dùng.

Hàng đợi serial và concurrent: so sánh

Hàng đợi serial thực thi tác vụ theo trình tự nghiêm ngặt, lần lượt từng tác vụ một. Nếu ba tác vụ được đặt vào hàng đợi serial, tác vụ thứ hai chỉ bắt đầu sau khi tác vụ thứ nhất hoàn thành hoàn toàn. Hàng đợi serial được sử dụng để đồng bộ hóa quyền truy cập vào tài nguyên dùng chung — ví dụ: một mảng được sửa đổi từ nhiều phần của mã.

Hàng đợi concurrent chạy nhiều tác vụ đồng thời, phân phối chúng qua các luồng có sẵn từ nhóm hệ thống. Các tác vụ trong hàng đợi concurrent bắt đầu theo thứ tự FIFO nhưng hoàn thành theo thứ tự ngẫu nhiên nếu thời gian thực thi của chúng khác nhau. Hàng đợi concurrent không đảm bảo thứ tự hoàn thành — chỉ đảm bảo thứ tự bắt đầu.

Tham sốHàng đợi serialHàng đợi concurrent
Thứ tự thực thiTuần tự nghiêm ngặtSong song
Số lượng luồngMộtNhiều từ nhóm GCD
Ứng dụngBảo vệ tài nguyên dùng chungTính toán độc lập
Hàng đợi chínhCó (luồng chính)Không
Rủi ro deadlockCao khi sync trên cùng hàng đợiThấp

Khi nào chọn hàng đợi serial

Hàng đợi serial lý tưởng cho các tác vụ sửa đổi trạng thái dùng chung — ghi vào tệp, cập nhật mô hình dữ liệu hoặc làm việc với Core Data. Sử dụng hàng đợi serial đảm bảo rằng hai phần mã không sửa đổi cùng một dữ liệu đồng thời, loại bỏ điều kiện cạnh tranh mà không cần khóa bổ sung.

Khi nào chọn hàng đợi concurrent

Hàng đợi concurrent phù hợp cho các tác vụ không phụ thuộc lẫn nhau: tải nhiều hình ảnh, yêu cầu mạng song song hoặc xử lý dữ liệu theo lô. GCD tự động quyết định số lượng tác vụ chạy đồng thời dựa trên số lõi CPU và tải hệ thống hiện tại.

Quality of Service: mức ưu tiên thực thi tác vụ

QoS (Quality of Service) là cơ chế GCD thông báo cho hệ điều hành về tầm quan trọng và mức độ khẩn cấp của tác vụ. Hệ thống sử dụng QoS để lập lịch luồng: các tác vụ có QoS cao hơn nhận được nhiều thời gian CPU hơn và bắt đầu sớm hơn. Giá trị QoS được truyền khi tạo hàng đợi hoặc gửi một tác vụ cụ thể.

GCD có năm lớp QoS khả dụng. .userInteractive — mức ưu tiên cao nhất cho các tác vụ liên quan đến UI. .userInitiated — cho các tác vụ do người dùng khởi tạo. .utility — cho các tác vụ nền hiển thị tiến trình. .background — cho các tác vụ không hiển thị với người dùng. .default — cấp trung gian giữa userInitiated và utility, được sử dụng mặc định.

Theo Apple (2026), chọn QoS sai là một trong những nguyên nhân phổ biến gây ra vấn đề về hiệu suất. Chạy tải xuống nền với QoS .userInteractive tiêu tốn tài nguyên của UI, gây ra độ trễ vi mô trong hoạt ảnh. Khuyến nghị chọn QoS thấp nhất vẫn cung cấp thời gian thực thi chấp nhận được.

Ví dụ sử dụng QoS

Khi tải hình ảnh để hiển thị ngay lập tức, hãy sử dụng .userInitiated — người dùng mong đợi kết quả. Để tải trước màn hình tiếp theo, .utility là đủ. Đồng bộ hóa nền với máy chủ được thực hiện với .background, giảm thiểu tác động đến các tác vụ đang hoạt động.

DispatchGroup và semaphore: đồng bộ hóa tác vụ

DispatchGroup cho phép theo dõi việc hoàn thành của một nhóm tác vụ. Khi tất cả các tác vụ trong nhóm hoàn thành, GCD gọi trình xử lý notify trên hàng đợi được chỉ định. Điều này đặc biệt hữu ích khi tải nhiều tài nguyên độc lập — dữ liệu hồ sơ, danh sách bạn bè và cài đặt — khi giao diện chỉ nên cập nhật sau khi nhận được tất cả dữ liệu.

DispatchGroup hỗ trợ lệnh gọi đồng bộ wait(), chặn luồng hiện tại cho đến khi tất cả các tác vụ hoàn thành. Điều này thuận tiện khi mã không thể tiếp tục mà không có kết quả của nhóm. Biến thể bất đồng bộ notify() gọi closure trên hàng đợi được chỉ định sau khi tất cả các tác vụ hoàn thành, mà không chặn luồng gọi.

DispatchSemaphore để giới hạn song song

DispatchSemaphore kiểm soát quyền truy cập vào tài nguyên bằng cách giới hạn số lượng truy cập đồng thời. Một semaphore với giá trị ban đầu là 3 cho phép tối đa ba tác vụ song song. Gọi wait() giảm bộ đếm, signal() tăng bộ đếm. Nếu bộ đếm bằng 0, luồng bị chặn cho đến khi tài nguyên khả dụng.

Ví dụ mã với DispatchQueue trong Swift

Hãy xem ba ví dụ thực tế về sử dụng DispatchQueue trong Swift. Ví dụ đầu tiên minh họa lệnh gọi async cơ bản với trở về luồng chính, ví dụ thứ hai hiển thị đồng bộ hóa qua hàng đợi serial, và ví dụ thứ ba sử dụng DispatchGroup cho các yêu cầu song song.

Lệnh gọi async cơ bản với trở về main

DispatchQueue.main là hàng đợi serial của luồng chính, dành riêng cho các thao tác UI. Luôn sử dụng nó để cập nhật giao diện sau khi hoàn thành công việc nền.

swift
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
    let data = self.fetchData()
    DispatchQueue.main.async {
        self.updateUI(with: data)
    }
}

Hàng đợi serial để bảo vệ tài nguyên dùng chung

Tạo một hàng đợi serial tùy chỉnh với định danh duy nhất đồng bộ hóa quyền truy cập vào mảng có thể thay đổi. Tất cả các thao tác đọc và ghi đều đi qua một hàng đợi duy nhất, loại bỏ điều kiện cạnh tranh.

swift
let serialQueue = DispatchQueue(label: "com.app.items")
var items: [Int] = []

serialQueue.async {
    items.append(1)
}
serialQueue.async {
    let last = items.last
    DispatchQueue.main.async {
        print("Last item: \(last)")
    }
}

DispatchGroup cho các yêu cầu song song

DispatchGroup cho phép khởi chạy nhiều tác vụ trên hàng đợi concurrent và nhận thông báo khi tất cả hoàn thành. Điều này hữu ích khi tải dữ liệu cho màn hình hồ sơ.

swift
let group = DispatchGroup()
let worker = DispatchQueue.global()

worker.async(group: group) { self.loadProfile() }
worker.async(group: group) { self.loadFriends() }
worker.async(group: group) { self.loadSettings() }

group.notify(queue: DispatchQueue.main) {
    self.showCompleteUI()
}

Lỗi thường gặp khi làm việc với DispatchQueue

Deadlock khi gọi sync trên hàng đợi serial là lỗi phổ biến nhất. Nếu một tác vụ trên hàng đợi serial gọi queue.sync trên cùng hàng đợi đó, luồng sẽ bị chặn vĩnh viễn. Hàng đợi chờ tác vụ hiện tại hoàn thành, và tác vụ chờ lệnh gọi sync hoàn thành — một sự chặn lẫn nhau kinh điển.

Cập nhật UI từ luồng nền

Tất cả các thao tác với UIKit phải được thực hiện trên luồng chính. Xcode phát hiện các lỗi này ở chế độ Debug thông qua Main Thread Checker. Trong bản Release, chúng dẫn đến hành vi không thể đoán trước: hoạt ảnh không chạy, UI không cập nhật, và có thể xảy ra sự cố.

Tạo quá nhiều hàng đợi tùy chỉnh

Tạo hàng trăm hàng đợi tùy chỉnh thay vì sử dụng hàng đợi toàn cục là một phản mẫu. Mỗi hàng đợi tiêu tốn tài nguyên hệ thống. Đối với hầu hết các tác vụ, hàng đợi concurrent toàn cục với các mức QoS khác nhau và một hoặc hai hàng đợi serial để đồng bộ hóa dữ liệu dùng chung là đủ.

Bỏ qua autoreleasepool trong vòng lặp

Khi thực thi các tác vụ vòng lặp tốn nhiều tài nguyên trên hàng đợi nền mà không có autoreleasepool, bộ nhớ tăng lên cho đến khi toàn bộ vòng lặp kết thúc. ARC chỉ giải phóng đối tượng khi thoát khỏi autorelease pool. Hãy bọc các lần lặp vòng lặp trong autoreleasepool { } để giải phóng bộ nhớ kịp thời.

Câu hỏi thường gặp

Sự khác biệt giữa DispatchQueue và OperationQueue là gì?

OperationQueue được xây dựng trên GCD nhưng cung cấp API cấp cao hơn với sự phụ thuộc hoạt động, KVO và hỗ trợ hủy. DispatchQueue là hàng đợi cấp thấp cho các tác vụ async đơn giản không có quản lý phụ thuộc.

Có thể buộc dừng một tác vụ trong DispatchQueue không?

GCD không hỗ trợ dừng một tác vụ đang chạy. Phương thức suspend() chỉ tạm dừng các tác vụ mới; tác vụ hiện tại chạy cho đến khi kết thúc. Hủy bỏ yêu cầu kiểm tra cờ thủ công bên trong mã tác vụ.

Nên chọn QoS nào cho yêu cầu mạng?

Cho yêu cầu chính với hiển thị kết quả ngay lập tức — .userInitiated. Cho tải trước dữ liệu — .utility. Cho đồng bộ hóa nền — .background.

Hàng đợi concurrent sử dụng bao nhiêu luồng?

GCD không cố định số lượng luồng. Nhóm luồng mở rộng linh hoạt dưới tải, xem xét lõi CPU, tải hiện tại và QoS của mỗi tác vụ. Số lượng tối đa bị giới hạn bởi hệ thống.

Tại sao DispatchQueue.main bắt buộc cho UIKit?

UIKit không an toàn cho luồng — tất cả các lớp của nó chỉ được gọi từ luồng chính. Vi phạm gây ra hành vi không thể đoán trước, bỏ lỡ cập nhật và sự cố trong Production.

Tổng kết

  • DispatchQueue là công cụ chính của Grand Central Dispatch cho các tác vụ bất đồng bộ trong iOS và macOS
  • Hàng đợi serial thực thi tác vụ tuần tự, loại bỏ điều kiện cạnh tranh mà không cần khóa
  • Hàng đợi concurrent chạy tác vụ song song qua nhóm luồng hệ thống
  • QoS xác định mức ưu tiên tác vụ — từ userInteractive đến background
  • DispatchGroup đồng bộ hóa nhiều tác vụ song song với notify trên luồng chính
  • Deadlock khi sync trên hàng đợi serial đang bận là lỗi nghiêm trọng cần chú ý
  • Luồng chính bắt buộc cho UIKit — chỉ cập nhật giao diện qua DispatchQueue.main

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm