latch.cpp 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include "common/latch.h"
  5. #include "common/check.h"
  6. namespace Carbon {
  7. auto Latch::Inc() -> void {
  8. // The increment must be _atomic_ but is _relaxed_.
  9. //
  10. // Increments and decrements can happen concurrently on separate threads, so
  11. // we need to prevent tearing and for there to be a total ordering of stores
  12. // to this atomic.
  13. //
  14. // However we provide no _synchronization_ of the increment with any other
  15. // operations. Instead, the caller must provide some extrinsic happens-before
  16. // between its call to `Inc` and its later call to `Dec`. When that call to
  17. // `Dec` synchronizes-with another call to `Dec`, all relaxed stores are
  18. // covered by the resulting inter-thread happens-before relationship.
  19. count_.fetch_add(1, std::memory_order_relaxed);
  20. }
  21. auto Latch::Dec() -> bool {
  22. // The decrement is both an _acquire_ and _release_ operation.
  23. //
  24. // All threads which decrement to a non-zero value need to synchronize-with
  25. // the thread which decrements to a zero value. This means the decrements to
  26. // non-zero values need to have _release_ semantics that are _acquired_ by the
  27. // decrement to zero. Since there is a single decrement operation, it must be
  28. // both _acquire_ and _release_.
  29. //
  30. // Note that this technically provides a stronger guarantee than the contract
  31. // of `Dec` requires -- *all* decrements synchronize with all decrements whose
  32. // value they observe, we only need that to be true of the decrement arriving
  33. // at zero. This could in theory be modeled by conditional fences, but those
  34. // have their own problems and we don't need to model the more precise
  35. // semantics for efficiency.
  36. auto previous = count_.fetch_sub(1, std::memory_order_acq_rel);
  37. CARBON_CHECK(previous > 0);
  38. if (previous == 1) {
  39. // Ensure that our closure is fully destroyed here, releasing any
  40. // resources, locks, or other synchronization primitives.
  41. auto on_zero = std::exchange(on_zero_, [] {});
  42. std::move(on_zero)();
  43. return true;
  44. }
  45. return false;
  46. }
  47. auto Latch::Init(llvm::unique_function<auto()->void> on_zero) -> Handle {
  48. CARBON_CHECK(count_ == 0);
  49. on_zero_ = std::move(on_zero);
  50. return Handle(this);
  51. }
  52. } // namespace Carbon