If you're going to go this route (which I think is a good idea), then why not use thread pools? The way this works is you hand a job/task to a threadpool object which handles allocating a new thread if needed or using an existing thread if enough are already allocated. So you just tell the pool to execute tasks. It scales well because the pool uses a configurable limit on the # of threads in the pool. So once this limit is reached, it queues the task and tasks in the queue run on existing threads in that case. Anyone know if thread pools are part of standard c++ libs or what would need to be included/written? But this is the standard/clean way to do this type of thing these days. That's about all I know about the subject, so... anyone know more about using them (have only used thread pools in .net).
However, a thread pool could be useful. Be careful, it should be a singleton (one instance).
And don't forget Search-in-files and TODO list! :D
OK, but if the code completion is going to use the thread pool, how long will I have to wait before the pool's ready so I can make the changes to CC? Or should I begin the changes and later we can switch to using the pool?
Why a singleton? You could have many different pools (if needed). Why restrict yourself to just one global pool?
One pool for code-completion, one for compiler, etc.
And the compiler could use thread pooling. Each wxExecute could be executed in a thread pool worker class...
Yiannis.
OK, but if the code completion is going to use the thread pool, how long will I have to wait before the pool's ready so I can make the changes to CC? Or should I begin the changes and later we can switch to using the pool?
class ParserTask: public ThreadTask
{
};
#ifndef CBTHREADPOOL_H
#define CBTHREADPOOL_H
#include <queue>
#include <vector>
#include <wx/event.h>
#include <wx/thread.h>
extern int idThreadPoolMsg;
// flags
#define DELETE_WORKER_WHEN_DONE 0x01
/// Abstract base class for workers assigned to the thread pool object
/// Derive a class from this and perform your thing in Execute()
/// It's like wxThread, except it's not a thread anymore ;)
class cbThreadPoolWorker
{
public:
virtual int Execute() = 0;
};
// forward decl
class PrivateThread;
/// Definition of a Thread Pool Object (used by thread pool only)
struct cbThreadPoolObject
{
cbThreadPoolObject() : pThread(0), pWorker(0), flags(0) {}
cbThreadPoolObject(const cbThreadPoolObject& rhs) : pThread(rhs.pThread), pWorker(rhs.pWorker), flags(rhs.flags) {}
PrivateThread* pThread;
cbThreadPoolWorker* pWorker;
int flags;
};
/// The main thread pool object.
class cbThreadPool
{
public:
cbThreadPool(wxEvtHandler* owner, int id = -1, int concurrentThreads = -1);
virtual ~cbThreadPool();
virtual void SetConcurrentThreads(int concurrentThreads);
virtual bool AddThread(cbThreadPoolWorker* worker, bool deleteWhenDone = false);
virtual void RemoveThread(cbThreadPoolWorker* worker);
protected:
virtual void AllocThreads();
virtual void FreeThreads();
virtual void ScheduleThreads();
void Log(const wxString& msg);
wxEvtHandler* m_pOwner;
int m_ID;
std::queue<cbThreadPoolObject> m_Pool;
std::vector<PrivateThread*> m_Workers; // worker threads
int m_ConcurrentThreads;
int m_MaxThreads;
};
/// Base thread class
class PrivateThread : public wxThread
{
public:
enum State
{
Idle,
Busy
};
enum Status
{
LaunchJob,
Quit
};
PrivateThread(cbThreadPoolWorker* worker = 0)
: m_Condition(m_Mutex),
m_pWorker(worker),
m_Status(Quit),
m_State(Idle)
{
m_Mutex.Lock();
}
~PrivateThread(){}
void SetWorker(cbThreadPoolWorker* worker);
void SetStatus(Status status);
State GetState(){ return m_State; }
virtual ExitCode Entry();
protected:
friend class cbThreadPool; // allow thread pool to access the mutex and the condition object
wxMutex m_Mutex;
wxCondition m_Condition;
cbThreadPoolWorker* m_pWorker;
Status m_Status;
State m_State;
private:
};
#endif // CBTHREADPOOL_H
class ParserThread : public cbThreadPoolWorker
{
...
int Execute(); // override pure virtual
...
};
// set the max nr of concurrent threads to run
// -1 means "number of CPUs" (yes it works with HyperThreading ;) )
g_ThreadPool.SetConcurrentThreads(-1);
// pass a new "thread" to the pool; it 'll run when the pool has available resources
g_ThreadPool.AddThread(new ParserThread);
If you're going to call delete on pointers to workers (as that #define implies) you should give them a virtual destructor, so that the derived class gets to run its own constructor as well ;).Code// flags
#define DELETE_WORKER_WHEN_DONE 0x01
/// Abstract base class for workers assigned to the thread pool object
/// Derive a class from this and perform your thing in Execute()
/// It's like wxThread, except it's not a thread anymore ;)
class cbThreadPoolWorker
{
public:
virtual int Execute() = 0;
};
If you're going to call delete on pointers to workers (as that #define implies) you should give them a virtual destructor, so that the derived class gets to run its own constructor as well ;).
I just now saw that second line. What is the purpose of the #define if there is a parameter that seems to specify the exact same thing?Code#define DELETE_WORKER_WHEN_DONE 0x01
[...]
virtual bool AddThread(cbThreadPoolWorker* worker, bool deleteWhenDone = false);
I just now saw that second line. What is the purpose of the #define if there is a parameter that seems to specify the exact same thing?Code#define DELETE_WORKER_WHEN_DONE 0x01
[...]
virtual bool AddThread(cbThreadPoolWorker* worker, bool deleteWhenDone = false);
So multiple thread pools looks needed. Is it possible to do something like:You do realize that would round to 0 if there's only one CPU, right? :?
#define THREADPOOL_COUNT 2
cpus = g_ThreadPool.GetCpuCount();
g_ThreadPool.SetConcurrentThreads(cpus / THREADPOOL_COUNT);
I don't think it should be a singleton. The pool of threads executes a queue of tasks, so independent modules (compile, code completion) would not want to wait to even start to execute if the other module has all the available threads occupied...
Your solution is worse: you dispose of half the computing power for each pool, with half worker threads going idle most of the time due to inactivity of the pool! Nonsense.
Mandrav, I don't see why we may delete tasks since they are supposed to be executed for a short time? It's not safe I think, since once the task is completed, the pointer could have been deleted!
You're going to want some way to signal the thread to terminate safely to cleanup resources (like say when the user closes the app).
Mandrav, you didn't answer my question about wxExecute and threads: is there a way to execute a command in a thread instead of a process? I can't find any... In fact, I found out that C::B is very resource consuming when used as a background task (compared to MSVC), I'm wondering if it's not related to the way compiler commands are executed (in processes instead of threads)...
NB: Currently wxExecute() can only be used from the main thread, calling this function from another thread will result in an assert failure in debug build and won't work.
#ifndef CBTHREADPOOL_H
#define CBTHREADPOOL_H
#include <queue>
#include <vector>
#include <wx/event.h>
#include <wx/thread.h>
/// Abstract base class for workers assigned to the thread pool object
/// Derive a class from this and perform your thing in Execute()
/// It's like wxThread...
class cbThreadPoolTask
{
public:
virtual ~cbThreadPoolTask(){}
virtual int Execute() = 0;
};
// forward decl
class PrivateThread;
/// The main thread pool object.
/// If you set concurrent threads to -1, it will use the number of CPUs present ;)
class cbThreadPool : public wxEvtHandler
{
public:
cbThreadPool(wxEvtHandler* owner, int id = -1, int concurrentThreads = -1);
virtual ~cbThreadPool();
virtual void SetConcurrentThreads(int concurrentThreads);
virtual bool AddTask(cbThreadPoolTask* worker);
protected:
virtual void AllocThreads();
virtual void FreeThreads();
virtual void ScheduleTasks();
virtual void ScheduleNextTask(PrivateThread* thr);
void OnThreadTaskDone(wxCommandEvent& event);
wxEvtHandler* m_pOwner;
int m_ID;
std::queue<cbThreadPoolTask*> m_Pool;
std::vector<PrivateThread*> m_Workers; // worker threads
int m_ConcurrentThreads;
int m_MaxThreads;
DECLARE_EVENT_TABLE();
};
#endif // CBTHREADPOOL_H
The term optimality I used previously refer to the article that nobody seems to have read, not a guess of what would be the better way to implement it and a guess on the number of worker threads to use. I'll stop making some comments if nobody read what I say :?
As I said,
- no need for many pools, it will degrade performance anyway: if one of the pool is already consuming all the processing power, the other pool will have some difficulties to run, and otherwise there will be many thread idle which consume (a little but...) processing power
- the number of thread should depend on the workload and workload type, not a fixed value which provide good performance for somebody but bad for others
Why keep the name "ParserThread" if it's not a Thread anymore? It's a bit confusing... but anyway, my kudos to you, in 48 hours you've done wonders with Codeblocks! 8)
One last question... why the "Please wait while blahblahblah" in the code completion? It's kinda annoying seeing it everytime.