Skip to content

Instantly share code, notes, and snippets.

@groue
Last active September 28, 2022 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save groue/cd2a3f5b18b0ddfa2da6db469fc20cc3 to your computer and use it in GitHub Desktop.
Save groue/cd2a3f5b18b0ddfa2da6db469fc20cc3 to your computer and use it in GitHub Desktop.
Semaphore.swift
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
Task {
do {
print("Start, 1 concurrent task")
let semaphore = Semaphore(value: 1)
await withThrowingTaskGroup(of: Void.self) { group in
for i in 0..<10 {
group.addTask {
try await semaphore.run {
print("Start task \(i)")
try await Task.sleep(nanoseconds: UInt64.random(in: 500_000_000...1_000_000_000))
print("End task \(i)")
}
}
}
}
print("Done")
await semaphore.run {
print("One last because it's fun")
}
}
do {
print("Start, 2 concurrent tasks")
let semaphore = Semaphore(value: 2)
await withThrowingTaskGroup(of: Void.self) { group in
for i in 0..<10 {
group.addTask {
try await semaphore.run {
print("Start task \(i)")
try await Task.sleep(nanoseconds: UInt64.random(in: 500_000_000...1_000_000_000))
print("End task \(i)")
}
}
}
}
print("Done")
await semaphore.run {
print("One last because it's fun")
}
}
do {
print("Start, 100 concurrent tasks")
let semaphore = Semaphore(value: 100)
await withThrowingTaskGroup(of: Void.self) { group in
for i in 0..<10 {
group.addTask {
try await semaphore.run {
print("Start task \(i)")
try await Task.sleep(nanoseconds: UInt64.random(in: 500_000_000...1_000_000_000))
print("End task \(i)")
}
}
}
}
print("Done")
await semaphore.run {
print("One last because it's fun")
}
}
}
/// A Semaphore for Swift Concurrency
///
/// https://forums.swift.org/t/actor-races/60439/47
public actor Semaphore {
private var value: Int
private var continuations: [UnsafeContinuation<Void, Never>] = []
init(value: Int) {
self.value = value
}
/// Waits for, or decrements, a semaphore.
///
/// Decrement the counting semaphore. If the resulting value is less than
/// zero, this function waits for a signal to occur before returning.
public func wait() async {
if value <= 0 {
await withUnsafeContinuation { continuation in
continuations.insert(continuation, at: 0)
}
}
assert(value > 0)
value -= 1
}
/// Signals (increments) a semaphore.
///
/// Increment the counting semaphore. If the previous value was less than
/// zero, this function wakes a task currently waiting in ``wait()``.
@discardableResult
public func signal() -> Int {
value += 1
if value > 0, let continuation = continuations.popLast() {
continuation.resume()
}
return value
}
/// Convenience method that waits, run an async function, and signals.
public func run<T>(execute: @Sendable @escaping () async throws -> T) async rethrows -> T {
await wait()
defer { signal() }
return try await execute()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment