1 module dast.async.thread; 2 3 import core.atomic, 4 core.thread; 5 6 private struct TaskBase { 7 void function(void*) run; 8 } 9 10 struct Task(alias fun, Args...) { 11 private: 12 TaskBase base = TaskBase(&impl); 13 Args _args; 14 15 static if (Args.length) { 16 this(Args args) { 17 _args = args; 18 } 19 } 20 21 static void impl(void* p) { 22 fun((cast(typeof(this)*)p)._args); 23 } 24 } 25 26 final class TaskThread : Thread { 27 this(void delegate() dg) { 28 super(dg); 29 } 30 31 void run() nothrow { 32 running.atomicStore(true); 33 super.start(); 34 } 35 36 shared bool running; 37 } 38 39 struct ThreadPool { 40 import core.memory, 41 core.sync, 42 tame.fixed.queue; 43 44 enum State : ubyte { 45 running, 46 done, 47 stop 48 } 49 50 private { 51 TaskThread[] pool; 52 Mutex mutex; 53 Condition cond; 54 shared State status; 55 Queue!(TaskBase*, 2048 / size_t.sizeof) queue; 56 shared uint nWorkers; 57 } 58 immutable uint timeoutMs; 59 60 this(uint size, uint timeout_ms = 0) @trusted { 61 timeoutMs = timeout_ms; 62 pool = (cast(TaskThread*)pureMalloc(size * TaskThread.sizeof))[0 .. size]; 63 // TODO: check pool is null 64 nWorkers = size; 65 mutex = new Mutex(); 66 cond = new Condition(mutex); 67 foreach (ref t; pool) { 68 t = new TaskThread(&workLoop); 69 t.run(); 70 } 71 } 72 73 ~this() @trusted { 74 pureFree(pool.ptr); 75 finish(true); 76 } 77 78 @property size() const => pool.length; 79 80 @property state() const @trusted => atomicLoad(status); 81 82 void workLoop() { 83 loop: while (atomicLoad(status) != State.stop) { 84 mutex.lock_nothrow(); 85 while (queue.empty) { 86 if (timeoutMs) { 87 if (!cond.wait(timeoutMs.msecs)) { 88 mutex.unlock_nothrow(); 89 break loop; 90 } 91 } else 92 cond.wait(); 93 } 94 auto task = queue.pop(); 95 cond.notify(); 96 mutex.unlock_nothrow(); 97 task.run(task); 98 } 99 auto tthis = Thread.getThis(); 100 foreach (t; pool) { 101 if (t == tthis) { 102 t.running.atomicStore(false); 103 atomicStore(nWorkers, nWorkers - 1); 104 break; 105 } 106 } 107 } 108 109 void finish(bool blocking = false) @trusted { 110 { 111 mutex.lock_nothrow(); 112 scope (exit) 113 mutex.unlock_nothrow(); 114 cas(&status, State.running, State.done); 115 cond.notifyAll(); 116 } 117 if (blocking) { 118 foreach (t; pool) { 119 t.join(); 120 } 121 } 122 } 123 124 void stop() @trusted { 125 mutex.lock_nothrow(); 126 scope (exit) 127 mutex.unlock_nothrow(); 128 cas(&status, State.running, State.stop); 129 cond.notifyAll(); 130 } 131 132 void run(alias fn, Args...)(auto ref Args args) { 133 run(&new Task!(fn, Args)(args).base); 134 } 135 136 private void run(TaskBase* task) 137 in (task) { 138 mutex.lock_nothrow(); 139 scope (exit) 140 mutex.unlock_nothrow(); 141 if (queue.full) 142 cond.wait(); 143 if (atomicLoad(status) != State.running) 144 return; 145 queue.push(task); 146 if (timeoutMs && atomicLoad(nWorkers) < pool.length) { 147 foreach (t; pool) { 148 if (!atomicLoad(t.running)) { 149 t.run(); 150 atomicStore(nWorkers, nWorkers + 1); 151 break; 152 } 153 } 154 } 155 cond.notify(); 156 } 157 }