载入中...
搜索中...
未找到
ThreadPool.cpp
浏览该文件的文档.
1#include "thread/ThreadPool.h"
2
3#include "common/Exception.h"
4#include "common/Module.h"
5#include "common/Capability.h"
7#include "thread/Channel.h"
8
9#include <algorithm>
10#include <chrono>
11#include <mutex>
12#include <system_error>
13#include <utility>
14
15namespace eve {
16namespace thread {
17
18namespace {
19constexpr int kMaxWorkerCount = 256;
20thread_local const void *currentPoolState = nullptr;
21std::mutex eventResolveMu;
22} // namespace
23
25 mutable std::mutex mu;
26 std::condition_variable cv;
27 std::condition_variable idleCv;
28 std::queue<std::shared_ptr<Task::State>> queue;
29 int busy = 0;
30 bool stopping = false;
31};
32
33ThreadPool::ThreadPool(int workerCount) {
34 if (workerCount <= 0) {
35 workerCount = static_cast<int>(std::thread::hardware_concurrency());
36 if (workerCount <= 0)
37 workerCount = 1;
38 }
39#if defined(__EMSCRIPTEN__)
40 // Emscripten spawns a real browser worker per pthread; hardware_concurrency
41 // on desktops reports 8-32 workers, which is wasteful (and exhausts the
42 // -sPTHREAD_POOL_SIZE pool). Cap to the pre-allocated pool size so the pool
43 // workers are reused instead of spawning more.
44 workerCount = std::min(workerCount, 4);
45#endif
46 if (workerCount > kMaxWorkerCount)
47 throw eve::Exception("ThreadPool worker count exceeds limit (%d)", kMaxWorkerCount);
48
49 workerCount_ = workerCount;
50 state_ = std::make_shared<State>();
51 workers_.reserve(static_cast<size_t>(workerCount_));
52 try {
53 for (int i = 0; i < workerCount_; ++i) {
54 auto state = state_;
55 workers_.emplace_back([state] { workerMain(state); });
56 }
57 } catch (...) {
58 {
59 std::lock_guard<std::mutex> lock(state_->mu);
60 state_->stopping = true;
61 }
62 state_->cv.notify_all();
63 for (auto &worker : workers_) {
64 if (worker.joinable())
65 worker.join();
66 }
67 workers_.clear();
68 throw;
69 }
70}
71
73 try {
74 stopImpl(true);
75 } catch (...) {
76 }
77}
78
79int ThreadPool::getWorkerCount() const { return workerCount_; }
80
82 std::lock_guard<std::mutex> lock(state_->mu);
83 return static_cast<int>(state_->queue.size());
84}
85
87 std::lock_guard<std::mutex> lock(state_->mu);
88 return !state_->stopping;
89}
90
91Task *ThreadPool::submit(std::function<void()> fn) {
92 if (!fn)
93 throw eve::Exception("ThreadPool::submit: null function");
94
95 auto state = std::make_shared<Task::State>(std::move(fn));
96 auto *task = new Task(state);
97 {
98 std::lock_guard<std::mutex> lock(state_->mu);
99 if (state_->stopping) {
100 delete task;
101 throw eve::Exception("ThreadPool is stopped");
102 }
103 state_->queue.push(std::move(state));
104 }
105 state_->cv.notify_one();
106 return task;
107}
108
110 if (ms < 0)
111 ms = 0;
112 return submit([ms] {
113 std::this_thread::sleep_for(std::chrono::milliseconds(ms));
114 });
115}
116
117Task *ThreadPool::submitPush(Channel *channel, std::string message, int delayMs) {
118 if (channel == nullptr)
119 throw eve::Exception("ThreadPool::submitPush: channel is null");
120 if (delayMs < 0)
121 delayMs = 0;
122 auto channelState = channel->state_;
123 return submit([channelState, msg = std::move(message), delayMs] {
124 if (delayMs > 0)
125 std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
126 {
127 std::lock_guard<std::mutex> lock(channelState->mu);
128 channelState->queue.push(msg);
129 }
130 channelState->cv.notify_one();
131 });
132}
133
134Task *ThreadPool::submitPost(std::string name, std::string data, int delayMs) {
135 if (name.empty())
136 throw eve::Exception("ThreadPool::submitPost: name must not be empty");
137 if (delayMs < 0)
138 delayMs = 0;
139 auto *poster = cap::query<caps::IMainThreadPost>();
140 if (!poster)
141 throw eve::Exception(
142 "ThreadPool::submitPost: no main-thread queue (event module not linked)");
143 {
144 // The provider may need the module registry, which is not thread-safe.
145 // Resolve while submitPost is still on the submitting (script/main) thread.
146 std::lock_guard<std::mutex> lock(eventResolveMu);
147 poster->prepare();
148 }
149 return submit([poster, name = std::move(name), data = std::move(data), delayMs] {
150 if (delayMs > 0)
151 std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
152 poster->postToMainThread(name, data);
153 });
154}
155
157 auto state = state_;
158 if (currentPoolState == state.get())
159 throw eve::Exception("ThreadPool::waitAll cannot be called from its worker");
160 std::unique_lock<std::mutex> lock(state->mu);
161 state->idleCv.wait(lock, [&state] { return state->queue.empty() && state->busy == 0; });
162}
163
165 if (currentPoolState == state_.get())
166 throw eve::Exception("ThreadPool::stop cannot be called from its worker");
167 stopImpl(false);
168}
169
170void ThreadPool::stopImpl(bool allowWorkerCaller) {
171 std::lock_guard<std::mutex> lifecycleLock(lifecycleMu_);
172 auto state = state_;
173 const bool calledByWorker = currentPoolState == state.get();
174 if (calledByWorker && !allowWorkerCaller)
175 throw eve::Exception("ThreadPool::stop cannot be called from its worker");
176
177 {
178 std::lock_guard<std::mutex> lock(state->mu);
179 state->stopping = true;
180 }
181 state->cv.notify_all();
182
183 // Destruction from a running job cannot safely join any pool worker: a
184 // different worker may itself be waiting for the current Task to finish.
185 // Workers only retain State, not ThreadPool, so detaching all of them here
186 // is safe and lets them drain accepted work before exiting.
187 if (calledByWorker) {
188 for (auto &worker : workers_) {
189 if (worker.joinable())
190 worker.detach();
191 }
192 workers_.clear();
193 return;
194 }
195
196 for (auto &w : workers_) {
197 if (!w.joinable())
198 continue;
199 try {
200 w.join();
201 } catch (const std::system_error &) {
202 if (w.joinable())
203 w.detach();
204 }
205 }
206 workers_.clear();
207}
208
209void ThreadPool::workerMain(std::shared_ptr<State> state) {
210 currentPoolState = state.get();
211 for (;;) {
212 std::shared_ptr<Task::State> task;
213 {
214 std::unique_lock<std::mutex> lock(state->mu);
215 state->cv.wait(lock, [&state] { return state->stopping || !state->queue.empty(); });
216 if (state->stopping && state->queue.empty()) {
217 currentPoolState = nullptr;
218 return;
219 }
220 task = std::move(state->queue.front());
221 state->queue.pop();
222 ++state->busy;
223 }
224
225 if (task)
226 Task::run(task);
227
228 {
229 std::lock_guard<std::mutex> lock(state->mu);
230 --state->busy;
231 if (state->queue.empty() && state->busy == 0)
232 state->idleCv.notify_all();
233 }
234 }
235}
236
237} // namespace thread
238} // namespace eve
int w
JobSystemThreadPool::State * state
Light2D::Data * data
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
Thread-safe message queue (love2d-style Channel). Values are strings so the API stays overload-free f...
Definition Channel.h:17
A job executed by a ThreadPool worker. Status strings (no enums): "pending" | "running" | "done" | "f...
Definition Task.h:16
void waitAll()
Block until idle. Throws when called by a worker belonging to this pool.
int getPendingCount() const
Task * submitSleep(int ms)
Sleep on a worker, then mark done — useful from scripts / tests.
ThreadPool(int workerCount)
void stop()
Stop accepting work and join workers. Worker calls are rejected. Idempotent.
Task * submitPost(std::string name, std::string data="", int delayMs=0)
Sleep, then post an Event on the main queue (thread-safe). Scripts poll via event....
Task * submitPush(Channel *channel, std::string message, int delayMs=0)
Sleep, then push a message onto a channel (cross-thread signalling).
Task * submit(std::function< void()> fn)
Submit a C++ callable. Caller owns the Task wrapper; work owns its shared state.
I * query()
Definition Capability.h:77
Definition Build.cpp:11
std::condition_variable cv
std::queue< std::shared_ptr< Task::State > > queue
std::condition_variable idleCv