Sources/Concurrency/CountDownLatch.swift (45 lines of code) (raw):
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// A concurrency utility class that allows coordination between threads. A count down latch
/// starts with an initial count. Threads can then decrement the count until it reaches zero,
/// at which point, the suspended waiting thread shall proceed. A `CountDownLatch` behaves
/// differently from a `DispatchSemaphore` once the latch is open. Unlike a semaphore where
/// subsequent waits would still block the caller thread, once a `CountDownLatch` is open, all
/// subsequent waits can directly passthrough.
public class CountDownLatch {
/// The initial count of the latch.
public let initialCount: Int
/// Initializer.
///
/// - parameter count: The initial count for the latch.
public init(count: Int) {
assert(count > 0, "CountDownLatch must have an initial count that is greater than 0.")
initialCount = count
conditionCount = AtomicInt(initialValue: count)
}
/// Decrements the latch's count, resuming all awaiting threads if the count reaches zero.
///
/// - note: If the latch is already open, invoking this method has no effects.
public func countDown() {
// Use `AtomicInt` to avoid contention during counting down and waiting. This allows the
// lock to be only acquired at the time when the latch switches from closed to open.
guard conditionCount.value > 0 else {
return
}
let newValue = conditionCount.decrementAndGet()
// Check for <= since multiple threads can perform decrements concurrently.
if newValue <= 0 {
condition.lock()
condition.broadcast()
condition.unlock()
}
}
/// Causes the current thread to suspend until the latch counts down to zero.
///
/// - note: If the current count is already zero, this method returns immediately without
/// suspending the current thread.
///
/// - parameter timeout: The optional timeout value in seconds. If the latch is not counted
/// down to zero before the timeout, this method returns false. If not defined, the current
/// thread will wait forever until the latch is counted down to zero.
/// - returns: true if the latch is counted down to zero. false if the timeout occurred before
/// the latch reaches zero.
@discardableResult
public func await(timeout: TimeInterval? = nil) -> Bool {
// Use `AtomicInt` to avoid contention during counting down and waiting. This allows the
// lock to be only acquired at the time when the latch switches from closed to open.
guard conditionCount.value > 0 else {
return true
}
let deadline: Date
if let timeout = timeout {
deadline = Date().addingTimeInterval(timeout)
} else {
deadline = Date.distantFuture
}
condition.lock()
defer {
condition.unlock()
}
// Check count again after acquiring the lock, before entering waiting. This ensures the caller
// does not enter waiting after the last counting down occurs.
// NSCondition must be run in a loop, since it can wake up randomly without any siganling.
while conditionCount.value > 0 {
let result = condition.wait(until: deadline)
if !result || Date() > deadline {
return false
}
}
return true
}
// MARK: - Private
private let condition = NSCondition()
// Use `AtomicInt` to avoid contention during counting down and waiting. This allows the
// lock to be only acquired at the time when the latch switches from closed to open.
private let conditionCount: AtomicInt
}