ThreadPool - Thread per task? or predefined number of threads?

Started by
3 comments, last by GanonPhantom 15 years, 8 months ago
Im currently building myself a threadpool (and before anyone says "There is a boost or .NET thread pool", i know...) But ive ran into a performance/scalability problem. It involves the number of threads the pool holds to complete tasks. I have come up with 2 options, but each one is strong where the other is weak. Such as, if i use the option of thread-per-task, it provides alot of scalability, but gives a small bit of overhead that has to be processed per-task... where as the predefined number of threads provide a small performance boost, and are a bit easier to manage, but are not to scalable when something such as a server comes under a large load of clients. I also thought about a SmartPool, in which it adds threads to the pool as it becomes overloaded. But, the flaw in that is, how do i tell when the thread is no longer needed? Thanks in advance, GanonPhantom
Advertisement
Quote:Original post by GanonPhantom

Such as, if i use the option of thread-per-task, it provides alot of scalability, but gives a small bit of overhead that has to be processed per-task... where as the predefined number of threads provide a small performance boost, and are a bit easier to manage, but are not to scalable when something such as a server comes under a large load of clients.


The usual approach is to have a fixed thread pool, then queue up the tasks.

Quote:I also thought about a SmartPool, in which it adds threads to the pool as it becomes overloaded. But, the flaw in that is, how do i tell when the thread is no longer needed?


Thread worker is something like this:
while (pool.alive()) {  Task * task = pool.getNextTask();  if (task) {    task->execute();  } else {    pool.waitForTasks();  }}


WaitForTasks waits on a semaphore, event, you can use something like notify_one() or equivalent to only wake one waiting worker.

Creating threads in an incredibly expensive operation, the biggest gains are to be made by not creating them.

Overhead or not, unless your tasks need to wait for something, such as IO, there isn't much performance to be gained from having more threads than there are CPUs. The advantage is only in somewhat simpler, or at very least sequential code, since OS breaks down your code into small chunks automatically.

For coarse-grained tasks, the overhead usually won't matter compared to benefit you get from using multiple cores without thread switching overhead.

Task-based paralelism is by far the most suitable for some servers. There's even boost::asio that's built on top of it and does all that you mention.
A problem ive come across with, when storing tasks, is being able to store more then 1 class type. Such as my task class is like

template<class TClass, typename TArg1> class Task {  public:   void Execute(TClass* Instance, void (TClass::*)(TArg1), TArg1 Argument);  protected:  private:};


and its hard to keep a queue of these... as there can be multiple classes in use.

Any ideas?

Also, i need some form a generic priority.. ie, this task can not be completed, untill X is done, but be able to do it through the threadpool, and not by always checking to see if the task is done by a ptr.

Thanks,
GanonPhantom
Quote:Original post by GanonPhantom
A problem ive come across with, when storing tasks, is being able to store more then 1 class type. Such as my task class is like

*** Source Snippet Removed ***

and its hard to keep a queue of these... as there can be multiple classes in use.


struct Task {  virtual ~Task();  virtual void execute() = 0;};


Quote:Also, i need some form a generic priority.. ie, this task can not be completed, untill X is done, but be able to do it through the threadpool, and not by always checking to see if the task is done by a ptr.


Don't overcomplicate things. For priorities, you can always use priority queue. There's so many problems once you go multi-threaded, that such complex issues soon get out of hand if approached generically.

And make sure to look at boost asio, since it allows for exactly this type of problem (strands - specify which tasks should execute in same thread), as well as use with generic callbacks (anything that has operator()(), boost::function as well).
Ah, i totally understand now, ive been looking at things in an odd way, so ive been trying to make it complicated.

Thanks a bunch!

This topic is closed to new replies.

Advertisement