forked from taskflow/taskflow
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlazy.hpp
More file actions
168 lines (155 loc) · 4.43 KB
/
Copy pathlazy.hpp
File metadata and controls
168 lines (155 loc) · 4.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#pragma once
#include "../taskflow.hpp"
#include <atomic>
#if __cplusplus >= TF_CPP20
#include <condition_variable>
#include <mutex>
#endif
namespace tf
{
// Lazy initializer that can be used with taskflow without deadlocking
template <class T>
class Lazy
{
/// The internal implementation that all instances share
struct LazyImpl
{
/// The cached result
// we cannot actually use std::variant here, since we cannot construct a function return
// value in-place that way
alignas(std::max(alignof(T), alignof(std::exception_ptr))) unsigned char m_data[std::max(
sizeof(T), sizeof(std::exception_ptr))];
enum class DataType : unsigned char
{
NONE = 0,
CALCULATING = 1,
VALUE = 2,
EXCEPTION = 3
};
/// Thread safety handling
std::atomic<DataType> m_dataType;
/// The function used to generate the result
const std::function<T()> m_fn;
tf::Executor& m_ex;
#if __cplusplus < TF_CPP20
std::condition_variable m_cv;
std::mutex m_mtx;
#endif
LazyImpl(std::function<T()> f, tf::Executor& ex) : m_dataType(DataType::NONE), m_fn(f), m_ex(ex) {}
~LazyImpl()
{
const DataType t = m_dataType.load(std::memory_order_acquire);
if (t == DataType::VALUE)
std::launder(reinterpret_cast<T*>(m_data))->~T();
else if (t == DataType::EXCEPTION)
std::launder(reinterpret_cast<std::exception_ptr*>(m_data))->~exception_ptr();
}
T* get()
{
DataType t = m_dataType.load(std::memory_order_acquire);
if (t <= DataType::CALCULATING)
{
// Nothing is yet calculated, need to calculate now
const int wid = m_ex.this_worker_id();
if (t == DataType::NONE
&& m_dataType.compare_exchange_strong(
t,
DataType::CALCULATING,
std::memory_order_release,
std::memory_order_acquire))
{
const auto calculateResult = [&]()
{
DataType res;
try
{
new (m_data) auto(m_fn());
res = DataType::VALUE;
}
catch (...)
{
new (m_data) auto(std::current_exception());
res = DataType::EXCEPTION;
}
#if __cplusplus >= TF_CPP20
m_dataType.store(res, std::memory_order_release);
m_dataType.notify_all();
#else
std::unique_lock l(m_mtx);
m_dataType.store(res, std::memory_order_release);
l.unlock();
m_cv.notify_all();
#endif
};
// Our thread is the first to arrive here, it should calculate result now
if (wid >= 0)
{
// If our current thread is from tf executor - we need to force
// threads to stop stealing older tasks to prevent deadlocks
m_ex.isolate(
std::make_shared<tf::TaskArena>(m_ex), calculateResult);
}
else
{
// If our thread is not visible in executor pool - run function as simple function
calculateResult();
}
}
else
{
if (wid >= 0)
{
// Other thread can help with task-stealing to the calculating thread,
// until result is available
m_ex.corun_until(
[&]() {
return m_dataType.load(std::memory_order_relaxed)
> DataType::CALCULATING;
});
}
else
{
// Other thread is not from taskflow, so they can only wait until result is
// available
#if __cplusplus >= TF_CPP20
m_dataType.wait(DataType::CALCULATING);
#else
{
std::unique_lock lk(m_mtx);
m_cv.wait(
lk,
[&] {
return m_dataType.load(std::memory_order_relaxed)
!= DataType::CALCULATING;
});
}
#endif
}
}
t = m_dataType.load(std::memory_order_acquire);
}
// At this point we hold either an exception or a value
if (t == DataType::EXCEPTION)
std::rethrow_exception(
*std::launder(reinterpret_cast<std::exception_ptr*>(m_data)));
return std::launder(reinterpret_cast<T*>(m_data));
}
};
public:
/// Pass a nullary (factory) function to be evaluated later.
template <typename Function>
Lazy(Function f, tf::Executor& ex): m_impl(std::make_shared<LazyImpl>(f, ex))
{
/// Returning a raw pointer here is bad behaviour
/// as it is not clear at all who owns the value
/// and who is responsible for deleting it.
static_assert(!std::is_pointer_v<T>, "Factory function should not return a raw pointer");
}
T const& operator*() const { return *m_impl->get(); }
T const* operator->() const { return m_impl->get(); }
/// Returns true if the result has been calculated
operator bool() const { return m_impl->m_data.has_value(); }
private:
std::shared_ptr<LazyImpl> m_impl;
};
}