GCD 基本概念延伸-Group、Semaphore、Timer、asyncAfter

Zhi-Hong Lin
10 min readNov 15, 2023

--

此篇延續上一篇 GCD 基本概念 簡介各種應用情境,例如使用 DispatchSourceTimer 進行精確計時,asyncAfter 在未來時間執行任務,DispatchGroup 確保多個異步任務完成後進行統一處理,DispatchSemaphore 控制並行程式碼的執行順序。

  • asyncAfter
  • DispatchSourceTimer
  • DispatchGroup
  • DispatchSemaphore

asyncAfter

用於在特定的時間後異步執行任務。它可以用來在未來的某個時間點執行一段程式碼。

wallDeadline 這是一個相對於系統啟動時間(即不受休眠和時間更改影響)的時間截止點。這樣的時間截止點通常用於需要精確計時,不受系統時間變更的影響的情況。例如,用於實現某個計時器功能。

let wallDeadline = DispatchWallTime.now() + .seconds(5)
DispatchQueue.global().asyncAfter(wallDeadline: wallDeadline) {
print("This code will be executed after 5 seconds using wall time.")
}

deadline 這是一個相對於系統的可休眠時間的時間截止點。這表示在休眠時,計時器會暫停,而不是繼續計時。這種時間截止點通常用於需要在休眠時保持相對時間的情況。

let deadline = DispatchTime.now() + .seconds(5)
DispatchQueue.global().asyncAfter(deadline: deadline) {
print("This code will be executed after 5 seconds using regular time.")
}

DispatchSourceTimer

DispatchSourceTimer 是 GCD 中的一個計時器工具,用於執行重複性或一次性的任務。相對於 Timer 更為精確,原因在於 Timer 受 Runloop 影響,如果 Timer 到了觸發時間,而這時候Runloop 正在處理其他耗時的任務,那麼本次 Timer 的調用會被漏掉。

class TimerExample {
private var timer: DispatchSourceTimer?

func startTimer() {
let queue = DispatchQueue.global(qos: .background)
timer = DispatchSource.makeTimerSource(queue: queue)
// 設定初次執行時間、間隔時間
timer?.schedule(deadline: .now(), repeating: .seconds(1), leeway: .milliseconds(100))
// 設定計時器執行的任務
timer?.setEventHandler { [weak self] in
self?.timerFired()
}
// 啟動計時器
timer?.resume()
}
func stopTimer() {
// 取消計時器
timer?.cancel()
timer = nil
}
private func timerFired() {
// 計時器執行的任務
print("Timer fired at \\(Date())")
}
}

let timerExample = TimerExample()
timerExample.startTimer()

DispatchGroup

DispatchGroup主要分成 enter(), leave()隊列調用 group 兩種方式,可以很方便的管理多項任務。比如多個網路請求同時發出去,等網路請求都完成後 reload UI

隊列調用 group

在這個示例中,我們首先創建一個 DispatchGroup,然後在一個 concurrent queue 上啟動了兩個異步任務。這兩個任務都與 group 相關聯,這意味著我們告訴 group 這些任務屬於同一個組。

let group = DispatchGroup()
let queue = DispatchQueue(label: "com.custom.queue", attributes: .concurrent)

// 開始第一個異步任務
queue.async(group: group) {
print("Task 1 started")
Thread.sleep(forTimeInterval: 2) // 模擬耗時操作
print("Task 1 finished")
}
// 開始第二個異步任務
queue.async(group: group) {
print("Task 2 started")
Thread.sleep(forTimeInterval: 1) // 模擬耗時操作
print("Task 2 finished")
}
// 等待所有任務完成
group.notify(queue: .main) {
print("All tasks are finished")
}

輸出結果如下:可以發現當兩個異步任務都完成時才會觸發 notify 方法

Task 2 started
Task 1 started
Task 2 finished
Task 1 finished
All tasks are finished

將操作改在異步任務執行

let group = DispatchGroup()
let queue = DispatchQueue(label: "concurrent", attributes: .concurrent)

queue.async(group: group) {
DispatchQueue.global().async {
print("Task 1 started")
Thread.sleep(forTimeInterval: 2) // 模擬耗時操作
print("Task 1 finished")
}
}
queue.async(group: group) {
DispatchQueue.global().async {
print("Task 2 started")
Thread.sleep(forTimeInterval: 2) // 模擬耗時操作
print("Task 2 finished")
}
}
// 等待所有任務完成
group.notify(queue: .main) {
print("All tasks are finished")
}

輸出結果如下:可以發現會直接觸發notify 方法,而不會等待異步任務都完成後

Task 1 started
Task 2 started
All tasks are finished
Task 2 finished
Task 1 finished

enter、leave

DispatchGroup 中的 enter()leave() 方法用於控制 Dispatch Group 中任務的進入和離開,以確保你可以正確地等待所有任務完成。

  • enter():當一個異步任務要加入到 Dispatch Group 中時,應該在任務的開始處調用 enter() 方法。這告訴 Dispatch Group,有一個新的任務要進入,需要追蹤它。
  • leave():當一個異步任務完成時,應該在任務的結尾處調用 leave() 方法。這告訴 Dispatch Group,該任務已經完成,可以將它從組中移除。

以下是使用 enter()leave() 的示例:

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

group.enter() // 進入組
queue.async {
print("Task 1 started")
Thread.sleep(forTimeInterval: 2) // 模擬耗時操作
print("Task 1 finished")

group.leave() // 離開組
}
group.enter() // 進入組
queue.async {
print("Task 2 started")
Thread.sleep(forTimeInterval: 1) // 模擬耗時操作
print("Task 2 finished")

group.leave() // 離開組
}
// 等待所有任務完成
group.notify(queue: .main) {
print("All tasks are finished")
}

這個示例中,我們使用 enter()leave() 在每個任務的開始和結尾處來管理 group。這確保 group 知道何時任務加入和離開,以便在所有任務完成時觸發 notify 回調。

DispatchSemaphore

用於控制並行程式碼的執行順序。它是一種計數信號量,用於限制同一時間允許訪問資源的執行緒或程式碼的數量。

DispatchSemaphore 具有兩個主要操作:

  1. wait():當呼叫 wait 時,如果計數器為零,它將阻止當前的執行緒,直到計數器增加為止。如果計數器大於零,則將計數器減一,並允許程式碼繼續執行。
  2. signal():當呼叫 signal 時,它將增加計數器的值。如果有一個或多個執行緒正在等待 wait,則其中一個將繼續執行。

假設你有一個資源,例如一個共享的資料結構,你想確保在同一時間最多只有三個執行緒可以訪問這個資源。你可以使用 DispatchSemaphore 來實現這一點。

let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 3)

func accessSharedResource(index: Int) {
semaphore.wait() // 等待信號量,如果信號量已經達到上限,則會阻止執行緒
concurrentQueue.async {
print("Accessing resource \\(index)")
// 執行某些操作,訪問共享資源
print("訪問資源 \\(index)")
// ...
semaphore.signal() // 釋放信號量,表示訪問結束,其他執行緒可以進入
}
}
for i in 0..<10 {
accessSharedResource(index: i)
}

在這個例子中,我們創建了一個名為 concurrentQueue 的併發隊列,並初始化了一個 DispatchSemaphore,其初始值為 3。然後,我們使用一個 for 迴圈啟動 10 個執行緒,每個執行緒嘗試訪問共享資源,但由於信號量的限制,同一時間最多允許三個執行緒同時訪問資源。其他執行緒必須等待,直到有一個執行緒釋放信號量後,才能進入臨界區。這樣可以確保資源的有序訪問。

Accessing resource 0
Accessing resource 1
訪問資源 0
訪問資源 1
Accessing resource 2
Accessing resource 3
訪問資源 2
訪問資源 3
Accessing resource 4
Accessing resource 6
Accessing resource 5
訪問資源 6
訪問資源 4
訪問資源 5
Accessing resource 7
訪問資源 7
Accessing resource 8
訪問資源 8
Accessing resource 9
訪問資源 9

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response