| 242 | : __semaphore__) |
| 243 | |
| 244 | class DecomissionableLastInFirstOutFixedSizeSemaphore |
| 245 | { |
| 246 | public: |
| 247 | // TODO(benh): enable specifying the number of threads that will use |
| 248 | // this semaphore. Currently this is difficult because we construct |
| 249 | // the `RunQueue` and later this class before we've determined the |
| 250 | // number of worker threads we'll create. |
| 251 | DecomissionableLastInFirstOutFixedSizeSemaphore() |
| 252 | { |
| 253 | for (size_t i = 0; i < semaphores.size(); i++) { |
| 254 | semaphores[i] = nullptr; |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | void signal() |
| 259 | { |
| 260 | // NOTE: we _always_ increment `count` which means that even if we |
| 261 | // try and signal a thread another thread might have come in and |
| 262 | // decremented `count` already. This is deliberate, but it would |
| 263 | // be interesting to also investigate the performance where we |
| 264 | // always signal a new thread. |
| 265 | count.fetch_add(1); |
| 266 | |
| 267 | while (waiters.load() > 0 && count.load() > 0) { |
| 268 | for (size_t i = 0; i < semaphores.size(); i++) { |
| 269 | // Don't bother finding a semaphore to signal if there isn't |
| 270 | // anybody to signal (`waiters` == 0) or anything to do |
| 271 | // (`count` == 0). |
| 272 | if (waiters.load() == 0 || count.load() == 0) { |
| 273 | return; |
| 274 | } |
| 275 | |
| 276 | // Try and find and then signal a waiter. |
| 277 | // |
| 278 | // TODO(benh): we `load()` first and then do a |
| 279 | // compare-and-swap because the read shouldn't require a lock |
| 280 | // instruction or synchronizing the bus. In addition, we |
| 281 | // should be able to optimize the loads in the future to a |
| 282 | // weaker memory ordering. That being said, if we don't see |
| 283 | // performance wins when trying that we should consider just |
| 284 | // doing a `std::atomic::exchange()` instead. |
| 285 | KernelSemaphore* semaphore = semaphores[i].load(); |
| 286 | if (semaphore != nullptr) { |
| 287 | if (!semaphores[i].compare_exchange_strong(semaphore, nullptr)) { |
| 288 | continue; |
| 289 | } |
| 290 | |
| 291 | // NOTE: we decrement `waiters` _here_ rather than in `wait` |
| 292 | // so that future signalers won't bother looping here |
| 293 | // (potentially for a long time) trying to find a waiter |
| 294 | // that might have already been signaled but just hasn't |
| 295 | // woken up yet. We even go as far as decrementing `waiters` |
| 296 | // _before_ we signal to really keep a thread from having to |
| 297 | // loop. |
| 298 | waiters.fetch_sub(1); |
| 299 | |
| 300 | semaphore->signal(); |
| 301 |
nothing calls this directly
no outgoing calls
no test coverage detected