Skip to content

Commit

Permalink
Alternative GCD-based solution with bounded maximum amount of paralle…
Browse files Browse the repository at this point in the history
…lism
  • Loading branch information
ole committed Mar 14, 2024
1 parent 2752771 commit 8c5fe3e
Showing 1 changed file with 46 additions and 1 deletion.
47 changes: 46 additions & 1 deletion Sources/App.swift
Expand Up @@ -4,7 +4,10 @@ import Vision
@main
struct Main {
static func main() async throws {
// Pick one of these variants:
try await performWork()
// try await performWorkWithUnboundedThreadExplosion()
// try await performWorkUsingGCD(maxConcurrency: 5)
}
}

Expand All @@ -31,7 +34,7 @@ func performWork() async throws {

/// Alternative implementation that "fixes" the deadlock
/// until you create so many child tasks that you exhaust the GCD limit.
func performWorkUsingGCD() async throws {
func performWorkWithUnboundedThreadExplosion() async throws {
let imageURL = findResourceInBundle("church-of-the-king-j9jZSqfH5YI-unsplash.jpg")!
try await withThrowingTaskGroup(of: (id: Int, faceCount: Int).self) { group in
// This "fixes" the deadlock at the cost of thread explosion.
Expand Down Expand Up @@ -61,6 +64,48 @@ func performWorkUsingGCD() async throws {
}
}

/// Alternative implementation that fixes the deadlock.
///
/// Based on:
///
/// - Gwendal Roué’s reply on the Swift forums: https://forums.swift.org/t/cooperative-pool-deadlock-when-calling-into-an-opaque-subsystem/70685/3
/// - Souroush Khanlou, The GCD handbook: https://khanlou.com/2016/04/the-GCD-handbook/
/// - Mike Rhodes, Limiting concurrent execution using GCD: https://web.archive.org/web/20160613023817/https://dx13.co.uk/articles/2016/6/4/limiting-concurrent-execution-using-gcd.html
func performWorkUsingGCD(maxConcurrency: Int) async throws {
let imageURL = findResourceInBundle("church-of-the-king-j9jZSqfH5YI-unsplash.jpg")!
try await withThrowingTaskGroup(of: (id: Int, faceCount: Int).self) { group in
let semaphore = DispatchSemaphore(value: maxConcurrency)
let semaphoreWaitQueue = DispatchQueue(label: "maxConcurrency control")
for i in 1...100 {
group.addTask {
print("Task \(i) starting")
return try await withUnsafeThrowingContinuation { c in
semaphoreWaitQueue.async {
semaphore.wait()
DispatchQueue.global().async {
defer {
semaphore.signal()
}
do {
let request = VNDetectFaceRectanglesRequest()
let requestHandler = VNImageRequestHandler(url: imageURL)
try requestHandler.perform([request])
let faces = request.results ?? []
c.resume(returning: (id: i, faceCount: faces.count))
} catch {
c.resume(throwing: error)
}
}
}
}
}
}
for try await (id, faceCount) in group {
print("Task \(id) detected \(faceCount) faces")
}
}
}

func findResourceInBundle(_ filename: String) -> URL? {
// The Bundle.module bundle has a different structure, depending on whether
// you build the program with SwiftPM (`swift build`) or with Xcode.
Expand Down

0 comments on commit 8c5fe3e

Please sign in to comment.