mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:10:12 +01:00
The standard `std::latch` is very restrictive in how it can be used, and this makes it hard to easily leverage for simple coordination between a set of dynamically scheduled tasks, where there isn't an interesting synchronizing "merge" or future result. This tool makes it easy to establish a latch, hand out handles to it, and once all are destroyed, take whatever relevant action. Note: this is split out of a larger change that uses it. I can wait until the use case is ready, but seemed nice to review this separately. --------- Co-authored-by: Dana Jansens <danakj@orodu.net>
61 lines
2.4 KiB
C++
61 lines
2.4 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include "common/latch.h"
|
|
|
|
#include "common/check.h"
|
|
|
|
namespace Carbon {
|
|
|
|
auto Latch::Inc() -> void {
|
|
// The increment must be _atomic_ but is _relaxed_.
|
|
//
|
|
// Increments and decrements can happen concurrently on separate threads, so
|
|
// we need to prevent tearing and for there to be a total ordering of stores
|
|
// to this atomic.
|
|
//
|
|
// However we provide no _synchronization_ of the increment with any other
|
|
// operations. Instead, the caller must provide some extrinsic happens-before
|
|
// between its call to `Inc` and its later call to `Dec`. When that call to
|
|
// `Dec` synchronizes-with another call to `Dec`, all relaxed stores are
|
|
// covered by the resulting inter-thread happens-before relationship.
|
|
count_.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
auto Latch::Dec() -> bool {
|
|
// The decrement is both an _acquire_ and _release_ operation.
|
|
//
|
|
// All threads which decrement to a non-zero value need to synchronize-with
|
|
// the thread which decrements to a zero value. This means the decrements to
|
|
// non-zero values need to have _release_ semantics that are _acquired_ by the
|
|
// decrement to zero. Since there is a single decrement operation, it must be
|
|
// both _acquire_ and _release_.
|
|
//
|
|
// Note that this technically provides a stronger guarantee than the contract
|
|
// of `Dec` requires -- *all* decrements synchronize with all decrements whose
|
|
// value they observe, we only need that to be true of the decrement arriving
|
|
// at zero. This could in theory be modeled by conditional fences, but those
|
|
// have their own problems and we don't need to model the more precise
|
|
// semantics for efficiency.
|
|
auto previous = count_.fetch_sub(1, std::memory_order_acq_rel);
|
|
|
|
CARBON_CHECK(previous > 0);
|
|
if (previous == 1) {
|
|
// Ensure that our closure is fully destroyed here, releasing any
|
|
// resources, locks, or other synchronization primitives.
|
|
auto on_zero = std::exchange(on_zero_, [] {});
|
|
std::move(on_zero)();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
auto Latch::Init(llvm::unique_function<auto()->void> on_zero) -> Handle {
|
|
CARBON_CHECK(count_ == 0);
|
|
on_zero_ = std::move(on_zero);
|
|
return Handle(this);
|
|
}
|
|
|
|
} // namespace Carbon
|