Lazy.h 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. #pragma once
  2. #include <atomic>
  3. #include <utility>
  4. namespace c10 {
  5. /**
  6. * Thread-safe lazy value with opportunistic concurrency: on concurrent first
  7. * access, the factory may be called by multiple threads, but only one result is
  8. * stored and its reference returned to all the callers.
  9. *
  10. * Value is heap-allocated; this optimizes for the case in which the value is
  11. * never actually computed.
  12. */
  13. template <class T>
  14. class OptimisticLazy {
  15. public:
  16. OptimisticLazy() = default;
  17. OptimisticLazy(const OptimisticLazy& other) {
  18. if (T* value = other.value_.load(std::memory_order_acquire)) {
  19. value_ = new T(*value);
  20. }
  21. }
  22. OptimisticLazy(OptimisticLazy&& other) noexcept
  23. : value_(other.value_.exchange(nullptr, std::memory_order_acq_rel)) {}
  24. ~OptimisticLazy() {
  25. reset();
  26. }
  27. template <class Factory>
  28. T& ensure(const Factory& factory) {
  29. if (T* value = value_.load(std::memory_order_acquire)) {
  30. return *value;
  31. }
  32. T* value = new T(factory());
  33. T* old = nullptr;
  34. if (!value_.compare_exchange_strong(
  35. old, value, std::memory_order_release, std::memory_order_acquire)) {
  36. delete value;
  37. value = old;
  38. }
  39. return *value;
  40. }
  41. // The following methods are not thread-safe: they should not be called
  42. // concurrently with any other method.
  43. OptimisticLazy& operator=(const OptimisticLazy& other) {
  44. *this = OptimisticLazy{other};
  45. return *this;
  46. }
  47. OptimisticLazy& operator=(OptimisticLazy&& other) noexcept {
  48. if (this != &other) {
  49. reset();
  50. value_.store(
  51. other.value_.exchange(nullptr, std::memory_order_acquire),
  52. std::memory_order_release);
  53. }
  54. return *this;
  55. }
  56. void reset() {
  57. if (T* old = value_.load(std::memory_order_relaxed)) {
  58. value_.store(nullptr, std::memory_order_relaxed);
  59. delete old;
  60. }
  61. }
  62. private:
  63. std::atomic<T*> value_{nullptr};
  64. };
  65. /**
  66. * Interface for a value that is computed on first access.
  67. */
  68. template <class T>
  69. class LazyValue {
  70. public:
  71. virtual ~LazyValue() = default;
  72. virtual const T& get() const = 0;
  73. };
  74. /**
  75. * Convenience thread-safe LazyValue implementation with opportunistic
  76. * concurrency.
  77. */
  78. template <class T>
  79. class OptimisticLazyValue : public LazyValue<T> {
  80. public:
  81. const T& get() const override {
  82. return value_.ensure([this] { return compute(); });
  83. }
  84. private:
  85. virtual T compute() const = 0;
  86. mutable OptimisticLazy<T> value_;
  87. };
  88. /**
  89. * Convenience immutable (thus thread-safe) LazyValue implementation for cases
  90. * in which the value is not actually lazy.
  91. */
  92. template <class T>
  93. class PrecomputedLazyValue : public LazyValue<T> {
  94. public:
  95. PrecomputedLazyValue(T value) : value_(std::move(value)) {}
  96. const T& get() const override {
  97. return value_;
  98. }
  99. private:
  100. T value_;
  101. };
  102. } // namespace c10