├── .gitignore ├── LICENSE ├── README.md ├── build ├── genie.exe └── genie.lua ├── create_solution_vs2017.bat └── src ├── lucy.cpp ├── lucy.h └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.out 31 | *.app 32 | 33 | tmp/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mikulas Florek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lucy Job System 2 | 3 | **This is outdated compared to [Lumix Engine](https://github.com/nem0/LumixEngine/blob/master/src/engine/job_system.h). Use that instead.** 4 | 5 | Fiber-based job system with extremely simple API. 6 | 7 | It's a standalone version of job system used in [Lumix Engine](https://github.com/nem0/lumixengine). 8 | Only Windows is supported now, although the Lumix Engine version has Linux support, I have to copy it here yet. 9 | 10 | ## Demo 11 | Create a solution with ```create_solution_vs2017.bat```. The solution is in ```build/tmp/```. Open the solution, compile and run. 12 | 13 | ```cpp 14 | #include "lucy.h" 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | std::mutex g_mutex; 21 | std::condition_variable g_finished; 22 | std::mutex g_finished_mutex; 23 | 24 | 25 | void print(const char* msg) 26 | { 27 | std::lock_guard lock(g_mutex); 28 | printf(msg); 29 | printf("\n"); 30 | } 31 | 32 | 33 | void jobB(void*) 34 | { 35 | print("B begin"); 36 | print("B end"); 37 | } 38 | 39 | 40 | void jobA(void*) 41 | { 42 | print("A begin"); 43 | lucy::SignalHandle finished = lucy::INVALID_HANDLE; 44 | for(int i = 0; i < 10; ++i) { 45 | lucy::run(nullptr, jobB, &finished); 46 | } 47 | lucy::wait(finished); 48 | print("A end"); 49 | g_finished.notify_all(); 50 | } 51 | 52 | 53 | int main(int argc, char* argv[]) 54 | { 55 | lucy::init(); 56 | lucy::run(nullptr, jobA, nullptr); 57 | std::unique_lock lck(g_finished_mutex); 58 | g_finished.wait(lck); 59 | lucy::shutdown(); 60 | } 61 | ``` 62 | 63 | ## Integration 64 | Just put lucy.h and lucy.cpp in your project and you are good to go. 65 | 66 | ## Docs 67 | 68 | See ```src/main.cpp``` for an example. 69 | 70 | ### Initialization / shutdown 71 | 72 | ```lucy::init()``` must be called before any other function from lucy namespace 73 | ```lucy::shutdown()``` call this when you don't want to run any more jobs. Make sure all jobs are finished before calling this, since it does not wait for jobs to finish. 74 | 75 | ### Jobs 76 | 77 | Job is a function pointer with associated void data pointer. When job is executed, the function is called and the data pointer is passed as a parameter to this function. 78 | 79 | ```lucy::run``` push a job to global queue 80 | 81 | ```cpp 82 | int value = 42; 83 | lucy::run(&value, [](void* data){ 84 | printf("%d", *(int*)data); 85 | }, nullptr); 86 | ``` 87 | 88 | This prints ```42```. Eventually, after the job is finished, a signal can be triggered, see signals for more information. 89 | 90 | ### Signals 91 | 92 | ```cpp 93 | lucy::SignalHandle signal = lucy::INVALID_HANDLE; 94 | lucy::wait(signal); // does not wait, since signal is invalid 95 | lucy::incSignal(&signal); 96 | lucy::wait(signal); // wait until someone calls lucy::decSignal, execute other jobs in the meantime 97 | ``` 98 | 99 | ```cpp 100 | lucy::SignalHandle signal = lucy::INVALID_HANDLE; 101 | for (int i = 0; i < N; ++i) { 102 | lucy::incSignal(&signal); 103 | } 104 | lucy::wait(signal); // wait until lucy::decSignal is called N-times 105 | ``` 106 | 107 | If a signal is passed to ```lucy::run``` (3rd parameter), then the signal is automatically incremented. It is decremented once all the job is finished. 108 | 109 | ```cpp 110 | lucy::SignalHandle finished = lucy::INVALID_HANDLE; 111 | for(int i = 0; i < 10; ++i) { 112 | lucy::run(nullptr, job_fn, &finished); 113 | } 114 | lucy::wait(finished); // wait for all 10 jobs to finish 115 | ``` 116 | 117 | There's no need to destroy signals, it's "garbage collected". 118 | 119 | Signals can be used as preconditions to run a job. It means a job starts only after the signal is signalled: 120 | 121 | ```cpp 122 | lucy::SignalHandle precondition = getPrecondition(); 123 | lucy::runEx(nullptr, job_fn, nullptr, precondition, lucy::ANY_WORKER); 124 | ``` 125 | 126 | is functionally equivalent to: 127 | 128 | ```cpp 129 | void job_fn(void*) { 130 | lucy::wait(precondition); 131 | dowork(); 132 | } 133 | lucy::run(nullptr, job_fn, nullptr); 134 | ``` 135 | 136 | However, the ```lucy::runEx``` version has better performance. 137 | 138 | Finally, a job can be pinned to specific worker thread. This is useful for calling APIs which must be called from the same thread, e.g. OpenGL functions or WinAPI UI functions. 139 | 140 | ```cpp 141 | lucy::runEx(nullptr, job_fn, nullptr, lucy::INVALID_HANDLE, 3); // run on worker thread 3 142 | ``` 143 | -------------------------------------------------------------------------------- /build/genie.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nem0/lucy_job_system/e1dc62928f7b4b73adbca0acdfd15f62b831ff39/build/genie.exe -------------------------------------------------------------------------------- /build/genie.lua: -------------------------------------------------------------------------------- 1 | solution "lucy" 2 | flags { "Cpp17" } 3 | configurations { "Debug", "RelWithDebInfo" } 4 | includedirs {"../src" } 5 | language "C++" 6 | location "tmp" 7 | targetdir "tmp/bin" 8 | platforms { "x64" } 9 | flags { "Symbols" } 10 | 11 | project "lucy" 12 | kind "ConsoleApp" 13 | files { "../src/**.h", "../src/**.cpp" } -------------------------------------------------------------------------------- /create_solution_vs2017.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd build 3 | genie.exe vs2017 4 | echo "Solution is in build/tmp/" 5 | pause -------------------------------------------------------------------------------- /src/lucy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "lucy.h" 7 | 8 | 9 | namespace lucy 10 | { 11 | 12 | 13 | static_assert(sizeof(u8) == 1); 14 | static_assert(sizeof(u32) == 4); 15 | static_assert(sizeof(u64) == 8); 16 | 17 | 18 | template constexpr int countOf(const T (&)[count]) 19 | { 20 | return count; 21 | }; 22 | 23 | 24 | namespace Fiber 25 | { 26 | 27 | 28 | #ifdef _WIN32 29 | using Event = HANDLE; 30 | typedef void* Handle; 31 | typedef void(__stdcall *FiberProc)(void*); 32 | #else 33 | typedef ucontext_t Handle; 34 | typedef void (*FiberProc)(void*); 35 | #endif 36 | constexpr void* INVALID_FIBER = nullptr; 37 | 38 | 39 | void initThread(FiberProc proc, Handle* handle); 40 | Handle create(int stack_size, FiberProc proc, void* parameter); 41 | void destroy(Handle fiber); 42 | void switchTo(Handle* from, Handle fiber); 43 | 44 | 45 | void initThread(FiberProc proc, Handle* out) 46 | { 47 | *out = ConvertThreadToFiber(nullptr); 48 | proc(nullptr); 49 | } 50 | 51 | 52 | Handle create(int stack_size, FiberProc proc, void* parameter) 53 | { 54 | return CreateFiber(stack_size, proc, parameter); 55 | } 56 | 57 | 58 | void destroy(Handle fiber) 59 | { 60 | DeleteFiber(fiber); 61 | } 62 | 63 | 64 | void switchTo(Handle* from, Handle fiber) 65 | { 66 | SwitchToFiber(fiber); 67 | } 68 | 69 | 70 | } // namespace Fiber 71 | 72 | 73 | #ifdef _WIN32 74 | using Event = HANDLE; 75 | using Thread = HANDLE; 76 | void trigger(Event e) { ::SetEvent(e); } 77 | void reset(Event e) { ::ResetEvent(e); } 78 | Event createEvent() { return ::CreateEvent(nullptr, TRUE, FALSE, nullptr); } 79 | 80 | void waitMultiple(Event e0, Event e1, u32 timeout_ms) { 81 | const HANDLE handles[2] = { e0, e1 }; 82 | ::WaitForMultipleObjects(2, handles, false, timeout_ms); 83 | } 84 | #else 85 | struct Event 86 | { 87 | pthread_mutex_t mutex; 88 | pthread_cond_t cond; 89 | bool signaled; 90 | bool manual_reset; 91 | }; 92 | #endif 93 | 94 | 95 | enum { 96 | HANDLE_ID_MASK = 0xffFF, 97 | HANDLE_GENERATION_MASK = 0xffFF0000 98 | }; 99 | 100 | 101 | struct Job 102 | { 103 | void (*task)(void*) = nullptr; 104 | void* data = nullptr; 105 | SignalHandle dec_on_finish; 106 | SignalHandle precondition; 107 | u8 worker_index; 108 | }; 109 | 110 | 111 | struct Signal { 112 | volatile int value; 113 | u32 generation; 114 | Job next_job; 115 | SignalHandle sibling; 116 | }; 117 | 118 | 119 | struct WorkerTask; 120 | 121 | 122 | struct FiberDecl 123 | { 124 | int idx; 125 | Fiber::Handle fiber; 126 | Job current_job; 127 | }; 128 | 129 | 130 | struct System 131 | { 132 | System() 133 | { 134 | m_signals_pool.resize(4096); 135 | m_free_queue.resize(4096); 136 | for(int i = 0; i < 4096; ++i) { 137 | m_free_queue[i] = i; 138 | m_signals_pool[i].sibling = INVALID_HANDLE; 139 | m_signals_pool[i].generation = 0; 140 | } 141 | } 142 | 143 | 144 | std::mutex m_sync; 145 | std::mutex m_job_queue_sync; 146 | Event m_work_signal; 147 | std::vector m_workers; 148 | std::vector m_job_queue; 149 | std::vector m_signals_pool; 150 | std::vector m_free_fibers; 151 | std::vector m_ready_fibers; 152 | std::vector m_free_queue; 153 | FiberDecl m_fiber_pool[512]; 154 | }; 155 | 156 | 157 | static System* g_system = nullptr; 158 | static thread_local WorkerTask* g_worker = nullptr; 159 | 160 | #pragma optimize( "", off ) 161 | WorkerTask* getWorker() 162 | { 163 | return g_worker; 164 | } 165 | #pragma optimize( "", on ) 166 | 167 | 168 | struct WorkerTask 169 | { 170 | WorkerTask(System& system, u8 worker_index) 171 | : m_system(system) 172 | , m_worker_index(worker_index) 173 | { 174 | m_work_signal = createEvent(); 175 | reset(m_work_signal); 176 | } 177 | 178 | 179 | static u32 run(WorkerTask* task) 180 | { 181 | return task->task(); 182 | } 183 | 184 | 185 | u32 task() 186 | { 187 | g_worker = this; 188 | Fiber::initThread(start, &m_primary_fiber); 189 | m_is_finished = true; 190 | return 0; 191 | } 192 | 193 | 194 | #ifdef _WIN32 195 | static void __stdcall start(void* data) 196 | #else 197 | static void start(void* data) 198 | #endif 199 | { 200 | g_system->m_sync.lock(); 201 | FiberDecl* fiber = g_system->m_free_fibers.back(); 202 | g_system->m_free_fibers.pop_back(); 203 | getWorker()->m_current_fiber = fiber; 204 | Fiber::switchTo(&getWorker()->m_primary_fiber, fiber->fiber); 205 | } 206 | 207 | 208 | bool m_finished = false; 209 | FiberDecl* m_current_fiber = nullptr; 210 | Fiber::Handle m_primary_fiber; 211 | System& m_system; 212 | std::vector m_job_queue; 213 | std::vector m_ready_fibers; 214 | u8 m_worker_index; 215 | bool m_is_finished = false; 216 | Event m_work_signal; 217 | std::thread m_thread; 218 | }; 219 | 220 | 221 | static SignalHandle allocateSignal() 222 | { 223 | const u32 handle = g_system->m_free_queue.back(); 224 | Signal& w = g_system->m_signals_pool[handle & HANDLE_ID_MASK]; 225 | w.value = 1; 226 | w.sibling = INVALID_HANDLE; 227 | w.next_job.task = nullptr; 228 | g_system->m_free_queue.pop_back(); 229 | 230 | return handle & HANDLE_ID_MASK | w.generation; 231 | } 232 | 233 | 234 | static void pushJob(const Job& job) 235 | { 236 | if (job.worker_index != ANY_WORKER) { 237 | WorkerTask* worker = g_system->m_workers[job.worker_index % g_system->m_workers.size()]; 238 | worker->m_job_queue.push_back(job); 239 | trigger(worker->m_work_signal); 240 | return; 241 | } 242 | g_system->m_job_queue.push_back(job); 243 | trigger(g_system->m_work_signal); 244 | } 245 | 246 | 247 | void trigger(SignalHandle handle) 248 | { 249 | std::lock_guard lock(g_system->m_sync); 250 | 251 | Signal& counter = g_system->m_signals_pool[handle & HANDLE_ID_MASK]; 252 | --counter.value; 253 | if (counter.value > 0) return; 254 | 255 | SignalHandle iter = handle; 256 | while (isValid(iter)) { 257 | Signal& signal = g_system->m_signals_pool[iter & HANDLE_ID_MASK]; 258 | if(signal.next_job.task) { 259 | std::lock_guard lock(g_system->m_job_queue_sync); 260 | pushJob(signal.next_job); 261 | } 262 | signal.generation = (((signal.generation >> 16) + 1) & 0xffFF) << 16; 263 | g_system->m_free_queue.push_back(iter & HANDLE_ID_MASK | signal.generation); 264 | signal.next_job.task = nullptr; 265 | iter = signal.sibling; 266 | } 267 | } 268 | 269 | 270 | static bool isSignalZero(SignalHandle handle, bool lock) 271 | { 272 | if (!isValid(handle)) return true; 273 | 274 | const u32 gen = handle & HANDLE_GENERATION_MASK; 275 | const u32 id = handle & HANDLE_ID_MASK; 276 | 277 | if (lock) g_system->m_sync.lock(); 278 | Signal& counter = g_system->m_signals_pool[id]; 279 | bool is_zero = counter.generation != gen || counter.value == 0; 280 | if (lock) g_system->m_sync.unlock(); 281 | return is_zero; 282 | } 283 | 284 | 285 | static void runInternal(void* data 286 | , void (*task)(void*) 287 | , SignalHandle precondition 288 | , bool lock 289 | , SignalHandle* on_finish 290 | , u8 worker_index) 291 | { 292 | Job j; 293 | j.data = data; 294 | j.task = task; 295 | j.worker_index = worker_index; 296 | j.precondition = precondition; 297 | 298 | if (lock) g_system->m_sync.lock(); 299 | j.dec_on_finish = [&]() -> SignalHandle { 300 | if (!on_finish) return INVALID_HANDLE; 301 | if (isValid(*on_finish) && !isSignalZero(*on_finish, false)) { 302 | ++g_system->m_signals_pool[*on_finish & HANDLE_ID_MASK].value; 303 | return *on_finish; 304 | } 305 | return allocateSignal(); 306 | }(); 307 | if (on_finish) *on_finish = j.dec_on_finish; 308 | 309 | if (!isValid(precondition) || isSignalZero(precondition, false)) { 310 | std::lock_guard lock(g_system->m_job_queue_sync); 311 | pushJob(j); 312 | } 313 | else { 314 | Signal& counter = g_system->m_signals_pool[precondition & HANDLE_ID_MASK]; 315 | if(counter.next_job.task) { 316 | const SignalHandle ch = allocateSignal(); 317 | Signal& c = g_system->m_signals_pool[ch & HANDLE_ID_MASK]; 318 | c.next_job = j; 319 | c.sibling = counter.sibling; 320 | counter.sibling = ch; 321 | } 322 | else { 323 | counter.next_job = j; 324 | } 325 | } 326 | 327 | if (lock) g_system->m_sync.unlock(); 328 | } 329 | 330 | 331 | void incSignal(SignalHandle* signal) 332 | { 333 | assert(signal); 334 | std::lock_guard lock(g_system->m_sync); 335 | 336 | if (isValid(*signal) && !isSignalZero(*signal, false)) { 337 | ++g_system->m_signals_pool[*signal & HANDLE_ID_MASK].value; 338 | } 339 | else { 340 | *signal = allocateSignal(); 341 | } 342 | } 343 | 344 | 345 | void decSignal(SignalHandle signal) 346 | { 347 | trigger(signal); 348 | } 349 | 350 | 351 | void run(void* data, void(*task)(void*), SignalHandle* on_finished) 352 | { 353 | runInternal(data, task, INVALID_HANDLE, true, on_finished, ANY_WORKER); 354 | } 355 | 356 | 357 | void runEx(void* data, void(*task)(void*), SignalHandle* on_finished, SignalHandle precondition, u8 worker_index) 358 | { 359 | runInternal(data, task, precondition, true, on_finished, worker_index); 360 | } 361 | 362 | 363 | #ifdef _WIN32 364 | static void __stdcall manage(void* data) 365 | #else 366 | static void manage(void* data) 367 | #endif 368 | { 369 | g_system->m_sync.unlock(); 370 | 371 | FiberDecl* this_fiber = (FiberDecl*)data; 372 | 373 | WorkerTask* worker = getWorker(); 374 | while (!worker->m_finished) { 375 | FiberDecl* fiber = nullptr; 376 | Job job; 377 | { 378 | std::lock_guard lock(g_system->m_job_queue_sync); 379 | 380 | if (!worker->m_ready_fibers.empty()) { 381 | fiber = worker->m_ready_fibers.back(); 382 | worker->m_ready_fibers.pop_back(); 383 | if (worker->m_ready_fibers.empty()) reset(worker->m_work_signal); 384 | } 385 | else if (!worker->m_job_queue.empty()) { 386 | job = worker->m_job_queue.back(); 387 | worker->m_job_queue.pop_back(); 388 | if (worker->m_job_queue.empty()) reset(worker->m_work_signal); 389 | } 390 | else if (!g_system->m_ready_fibers.empty()) { 391 | fiber = g_system->m_ready_fibers.back(); 392 | g_system->m_ready_fibers.pop_back(); 393 | if (g_system->m_ready_fibers.empty()) reset(g_system->m_work_signal); 394 | } 395 | else if(!g_system->m_job_queue.empty()) { 396 | job = g_system->m_job_queue.back(); 397 | g_system->m_job_queue.pop_back(); 398 | if (g_system->m_job_queue.empty()) reset(g_system->m_work_signal); 399 | } 400 | } 401 | 402 | if (fiber) { 403 | worker->m_current_fiber = fiber; 404 | 405 | g_system->m_sync.lock(); 406 | g_system->m_free_fibers.push_back(this_fiber); 407 | Fiber::switchTo(&this_fiber->fiber, fiber->fiber); 408 | g_system->m_sync.unlock(); 409 | 410 | worker = getWorker(); 411 | worker->m_current_fiber = this_fiber; 412 | continue; 413 | } 414 | 415 | if (job.task) { 416 | this_fiber->current_job = job; 417 | job.task(job.data); 418 | this_fiber->current_job.task = nullptr; 419 | if (isValid(job.dec_on_finish)) { 420 | trigger(job.dec_on_finish); 421 | } 422 | worker = getWorker(); 423 | } 424 | else 425 | { 426 | waitMultiple(g_system->m_work_signal, worker->m_work_signal, 1); 427 | } 428 | } 429 | Fiber::switchTo(&getWorker()->m_current_fiber->fiber, getWorker()->m_primary_fiber); 430 | } 431 | 432 | 433 | bool init(u8 workers_count) 434 | { 435 | assert(!g_system); 436 | 437 | g_system = new System; 438 | reset(g_system->m_work_signal); 439 | 440 | g_system->m_free_fibers.reserve(countOf(g_system->m_fiber_pool)); 441 | for (FiberDecl& fiber : g_system->m_fiber_pool) { 442 | g_system->m_free_fibers.push_back(&fiber); 443 | } 444 | 445 | const int fiber_num = countOf(g_system->m_fiber_pool); 446 | for(int i = 0; i < fiber_num; ++i) { 447 | FiberDecl& decl = g_system->m_fiber_pool[i]; 448 | decl.fiber = Fiber::create(64 * 1024, manage, &g_system->m_fiber_pool[i]); 449 | decl.idx = i; 450 | } 451 | 452 | unsigned count = workers_count; 453 | if (count == 0) count = 1; 454 | for (unsigned i = 0; i < count; ++i) { 455 | WorkerTask* task = new WorkerTask(*g_system, i < 64 ? u64(1) << i : 0); 456 | 457 | try { 458 | std::thread t(&WorkerTask::run, task); 459 | task->m_thread.swap(t); 460 | g_system->m_workers.push_back(task); 461 | } 462 | catch(...) { 463 | assert(false); 464 | delete task; 465 | } 466 | } 467 | 468 | return !g_system->m_workers.empty(); 469 | } 470 | 471 | 472 | int getWorkersCount() 473 | { 474 | return (int)g_system->m_workers.size(); 475 | } 476 | 477 | 478 | void shutdown() 479 | { 480 | if (!g_system) return; 481 | 482 | for (WorkerTask* task : g_system->m_workers) 483 | { 484 | WorkerTask* wt = (WorkerTask*)task; 485 | wt->m_finished = true; 486 | } 487 | 488 | for (WorkerTask* task : g_system->m_workers) 489 | { 490 | while (!task->m_is_finished) trigger(g_system->m_work_signal); 491 | task->m_thread.join(); 492 | delete task; 493 | } 494 | 495 | for (FiberDecl& fiber : g_system->m_fiber_pool) 496 | { 497 | Fiber::destroy(fiber.fiber); 498 | } 499 | 500 | delete g_system; 501 | g_system = nullptr; 502 | } 503 | 504 | 505 | void wait(SignalHandle handle) 506 | { 507 | g_system->m_sync.lock(); 508 | if (isSignalZero(handle, false)) { 509 | g_system->m_sync.unlock(); 510 | return; 511 | } 512 | 513 | assert(getWorker()); 514 | 515 | FiberDecl* this_fiber = getWorker()->m_current_fiber; 516 | 517 | runInternal(this_fiber, [](void* data){ 518 | std::lock_guard lock(g_system->m_job_queue_sync); 519 | FiberDecl* fiber = (FiberDecl*)data; 520 | if (fiber->current_job.worker_index == ANY_WORKER) { 521 | g_system->m_ready_fibers.push_back(fiber); 522 | trigger(g_system->m_work_signal); 523 | } 524 | else { 525 | WorkerTask* worker = g_system->m_workers[fiber->current_job.worker_index % g_system->m_workers.size()]; 526 | worker->m_ready_fibers.push_back(fiber); 527 | trigger(worker->m_work_signal); 528 | } 529 | }, handle, false, nullptr, 0); 530 | 531 | FiberDecl* new_fiber = g_system->m_free_fibers.back(); 532 | g_system->m_free_fibers.pop_back(); 533 | getWorker()->m_current_fiber = new_fiber; 534 | Fiber::switchTo(&this_fiber->fiber, new_fiber->fiber); 535 | getWorker()->m_current_fiber = this_fiber; 536 | g_system->m_sync.unlock(); 537 | 538 | #ifndef NDEBUG 539 | g_system->m_sync.lock(); 540 | assert(isSignalZero(handle, false)); 541 | g_system->m_sync.unlock(); 542 | #endif 543 | } 544 | 545 | 546 | } // namespace lucy 547 | 548 | -------------------------------------------------------------------------------- /src/lucy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifndef LUCY_API 5 | #define LUCY_API 6 | #endif 7 | 8 | 9 | namespace lucy 10 | { 11 | 12 | 13 | using u8 = unsigned char; 14 | using u32 = unsigned; 15 | using u64 = unsigned long long; 16 | using SignalHandle = u32; 17 | constexpr u8 ANY_WORKER = 0xff; 18 | constexpr SignalHandle INVALID_HANDLE = 0xffFFffFF; 19 | 20 | 21 | LUCY_API bool init(u8 workers_count); 22 | LUCY_API void shutdown(); 23 | LUCY_API int getWorkersCount(); 24 | 25 | LUCY_API void incSignal(SignalHandle* signal); 26 | LUCY_API void decSignal(SignalHandle signal); 27 | 28 | LUCY_API void run(void* data, void(*task)(void*), SignalHandle* on_finish); 29 | LUCY_API void runEx(void* data, void (*task)(void*), SignalHandle* on_finish, SignalHandle precondition, u8 worker_index); 30 | LUCY_API void wait(SignalHandle waitable); 31 | LUCY_API inline bool isValid(SignalHandle waitable) { return waitable != INVALID_HANDLE; } 32 | 33 | 34 | template 35 | void runOnWorkers(F& f) 36 | { 37 | SignalHandle signal = JobSystem::INVALID_HANDLE; 38 | for(int i = 0, c = getWorkersCount(); i < c; ++i) { 39 | JobSystem::run(&f, [](void* data){ 40 | (*(F*)data)(); 41 | }, &signal); 42 | } 43 | wait(signal); 44 | } 45 | 46 | 47 | template 48 | void forEach(unsigned count, F& f) 49 | { 50 | struct Data { 51 | F* f; 52 | volatile i32 offset = 0; 53 | uint count; 54 | } data; 55 | data.count = count; 56 | data.f = &f; 57 | 58 | SignalHandle signal = JobSystem::INVALID_HANDLE; 59 | for(uint i = 0; i < count; ++i) { 60 | JobSystem::run(&data, [](void* ptr){ 61 | Data& data = *(Data*)ptr; 62 | for(;;) { 63 | const uint idx = MT::atomicIncrement(&data.offset) - 1; 64 | if(idx >= data.count) break; 65 | (*data.f)(idx); 66 | } 67 | }, &signal); 68 | } 69 | wait(signal); 70 | } 71 | 72 | 73 | } // namespace lucy 74 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "lucy.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | std::mutex g_mutex; 9 | std::condition_variable g_finished; 10 | std::mutex g_finished_mutex; 11 | 12 | 13 | void print(const char* msg) 14 | { 15 | std::lock_guard lock(g_mutex); 16 | printf(msg); 17 | printf("\n"); 18 | } 19 | 20 | 21 | void jobB(void*) 22 | { 23 | print("B begin"); 24 | print("B end"); 25 | } 26 | 27 | 28 | void jobA(void*) 29 | { 30 | print("A begin"); 31 | lucy::SignalHandle finished = lucy::INVALID_HANDLE; 32 | for(int i = 0; i < 10; ++i) { 33 | lucy::run(nullptr, jobB, &finished); 34 | } 35 | lucy::wait(finished); 36 | print("A end"); 37 | g_finished.notify_all(); 38 | } 39 | 40 | 41 | int main(int argc, char* argv[]) 42 | { 43 | lucy::init(std::thread::hardware_concurrency()); 44 | lucy::run(nullptr, jobA, nullptr); 45 | std::unique_lock lck(g_finished_mutex); 46 | g_finished.wait(lck); 47 | lucy::shutdown(); 48 | } --------------------------------------------------------------------------------