fix(threadpool): batches run every task once, and Stop ends the whole pool
- Enqueue(vector) dropped tasks when the count didn't divide by the workers: the last one got only the remainder. Tasks are now split as evenly as the count allows, each worker's taskCount grows by what it was given (the single-task Enqueue picks by it), and Start(1), which has no workers, leaves the batch for the caller's JoinPool. - Stop wakes every slot, JoinPool's too, after joining the workers: a thread that had joined the pool slept on. It takes each slot's mutex before notifying, so a waiter between its check and its sleep can't miss the wake-up, and works on the pool it started with. Again, or before Start, it does nothing; joining a thread twice threw. Its documentation says what it doesn't do: stop from a pool task, or from two threads at once, run tasks enqueued once it has begun, or have JoinPool wait for it. - Tests: EnqueueBatch, StopJoinPool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0837bbab4c
commit
f71cb43e85
5 changed files with 308 additions and 149 deletions
|
|
@ -1,32 +1,37 @@
|
|||
//SPDX-License-Identifier: LGPL-3.0-only
|
||||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Thread:ThreadPool;
|
||||
import std;
|
||||
|
||||
namespace Crafter {
|
||||
struct ThreadStorage {
|
||||
std::vector<std::function<void(void)>> tasks;
|
||||
std::vector<std::function<void(void)>> buffer;
|
||||
std::condition_variable cv;
|
||||
std::mutex mutex;
|
||||
std::atomic<unsigned int> taskCount;
|
||||
std::thread thread;
|
||||
};
|
||||
|
||||
export class ThreadPool {
|
||||
public:
|
||||
static inline std::atomic<bool> stopped;
|
||||
static void Start(int maxThreads = -1);
|
||||
static void Stop();
|
||||
static void Enqueue(std::function<void(void)> task);
|
||||
static void Enqueue(std::vector<std::function<void(void)>> tasks);
|
||||
static void JoinPool();
|
||||
static inline unsigned int threadCount;
|
||||
static inline std::atomic<unsigned int> activeThreads;
|
||||
private:
|
||||
static inline std::unordered_map<std::thread::id, ThreadStorage*> threadIdMap;
|
||||
static inline ThreadStorage* threadStorage;
|
||||
static void WaitAndExecuteTask(ThreadStorage* threadStorage);
|
||||
};
|
||||
//SPDX-License-Identifier: LGPL-3.0-only
|
||||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Thread:ThreadPool;
|
||||
import std;
|
||||
|
||||
namespace Crafter {
|
||||
struct ThreadStorage {
|
||||
std::vector<std::function<void(void)>> tasks;
|
||||
std::vector<std::function<void(void)>> buffer;
|
||||
std::condition_variable cv;
|
||||
std::mutex mutex;
|
||||
std::atomic<unsigned int> taskCount;
|
||||
std::thread thread;
|
||||
};
|
||||
|
||||
export class ThreadPool {
|
||||
public:
|
||||
static inline std::atomic<bool> stopped;
|
||||
static void Start(int maxThreads = -1);
|
||||
// Stops the workers, each once it has run what it was given, and ends
|
||||
// a JoinPool; a task enqueued once Stop has begun may not run. From
|
||||
// one thread at a time, not the pool's own: a task can't stop the
|
||||
// pool it runs in. Again, or before Start, it does nothing. JoinPool
|
||||
// can return before Stop has: Start a new pool once Stop returned.
|
||||
static void Stop();
|
||||
static void Enqueue(std::function<void(void)> task);
|
||||
static void Enqueue(std::vector<std::function<void(void)>> tasks);
|
||||
static void JoinPool();
|
||||
static inline unsigned int threadCount;
|
||||
static inline std::atomic<unsigned int> activeThreads;
|
||||
private:
|
||||
static inline std::unordered_map<std::thread::id, ThreadStorage*> threadIdMap;
|
||||
static inline ThreadStorage* threadStorage;
|
||||
static void WaitAndExecuteTask(ThreadStorage* threadStorage);
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue