mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 09:45:24 +02:00
- ~ThreadManager() never stopped worker threads before freeing the global context. If Teardown() wasn't reached before process exit (e.g. an early return between Init() and a caller's shutdown path), the still-running threads kept touching freed mutexes/queues/condvars, causing intermittent crashes on exit. The destructor now tears down if still initialized. - Teardown() silently leaked any non-cancellable task still sitting in a queue (global or per-thread) at shutdown time - such tasks were never run nor released, since nothing drains those queues once the worker threads have been marked cancelled/joined. Now every queued task is properly cancelled-or-warned and released. - Added an assert against a divide-by-zero in EnqueueTask's round-robin fallback, which would trigger if Init() were ever called with zero compute threads. - Removed Task::id()/Kind() and ThreadManager::TryCancelTask(), which were unused dead code (TryCancelTask was a no-op with no callers). - Fixed a stale comment on EnqueueTaskOnThread referencing a parameter that doesn't exist, and an auto-typed ternary that misleadingly read like a deque array copy (it's a pointer, not a copy). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4QAoxV2KY7ek4PcZw3WvY
84 lines
2.1 KiB
C++
84 lines
2.1 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
|
|
// The new threadpool.
|
|
|
|
// To help smart scheduling.
|
|
enum class TaskType {
|
|
CPU_COMPUTE,
|
|
IO_BLOCKING, // NOTE: Only these can access scoped storage on Android (they initialize the JNI context).
|
|
DEDICATED_THREAD, // These can never get stuck in queue behind others, but are more expensive to launch. Cannot use I/O.
|
|
};
|
|
|
|
enum class TaskPriority {
|
|
HIGH = 0,
|
|
NORMAL = 1,
|
|
LOW = 2,
|
|
|
|
COUNT,
|
|
};
|
|
|
|
// Implement this to make something that you can run on the thread manager.
|
|
class Task {
|
|
public:
|
|
virtual ~Task() {}
|
|
virtual TaskType Type() const = 0;
|
|
virtual TaskPriority Priority() const = 0;
|
|
virtual void Run() = 0;
|
|
virtual bool Cancellable() const { return false; }
|
|
virtual void Cancel() {}
|
|
virtual void Release() { delete this; }
|
|
};
|
|
|
|
class Waitable {
|
|
public:
|
|
virtual ~Waitable() {}
|
|
|
|
virtual void Wait() = 0;
|
|
|
|
void WaitAndRelease() {
|
|
Wait();
|
|
delete this;
|
|
}
|
|
};
|
|
|
|
struct TaskThreadContext;
|
|
struct GlobalThreadContext;
|
|
|
|
class ThreadManager {
|
|
public:
|
|
ThreadManager();
|
|
~ThreadManager();
|
|
|
|
// The distinction here is to be able to take hyper-threading into account.
|
|
// It gets even trickier when you think about mobile chips with BIG/LITTLE, but we'll
|
|
// just ignore it and let the OS handle it.
|
|
void Init(int numCores, int numLogicalCoresPerCpu);
|
|
void EnqueueTask(Task *task);
|
|
// Directly assigns a task to a specific worker thread's private queue, bypassing
|
|
// the global queue and load balancing. Used to pin related tasks (e.g. tiles of a
|
|
// parallel loop) to distinct threads by index.
|
|
void EnqueueTaskOnThread(int threadNum, Task *task);
|
|
void Teardown();
|
|
|
|
bool IsInitialized() const;
|
|
|
|
// Parallel loops (assumed compute-limited) get one thread per logical core. We have a few extra threads too
|
|
// for I/O bounds tasks, that can be run concurrently with those.
|
|
int GetNumLooperThreads() const;
|
|
|
|
private:
|
|
void TeardownTask(Task *task);
|
|
|
|
// This is always pointing to a context, initialized in the constructor.
|
|
GlobalThreadContext *global_;
|
|
|
|
int numThreads_ = 0;
|
|
int numComputeThreads_ = 0;
|
|
|
|
friend struct TaskThreadContext;
|
|
};
|
|
|
|
extern ThreadManager g_threadManager;
|