[Webserver]——Principle of thread pool, handwritten thread pool

Table of contents

1. What is a thread pool?

2. The role of thread pool

3. Design of task queue

4. Constructor

5.push interface design

6. Execution function of child thread

7. Destructor

8. Test the thread pool

9. Setting the number of threads in the thread pool

1.Experience value

2. Optimum number of threads algorithm


1. What is a thread pool?

 

Thread pool is a thread management technology implemented using the idea of ​​pooling technology . It is mainly used to reuse threads , conveniently manage threads and tasks, and decouple the creation of threads from the execution of tasks . We can create a thread pool to reuse already created threads to reduce resource consumption caused by frequent creation and destruction of threads.

 

2. The role of thread pool

  • Reuse thread resources;
  • Reduce the overhead of thread creation and destruction ;
  • The tasks of the producer thread can be processed asynchronously;
  • Reduced execution time for multiple tasks (not one task)
  • Balanced selection of thread resource overhead and CPU resources

3. Design of task queue

The task queue of the thread pool can be designed in containers such as arrays, linked lists, queues, and stacks.

I open up a continuous space for the task queue in advance as a circular task queue . This eliminates the need to allocate memory frequently and reduces memory fragmentation.

 

//任务队列
//T是任务
template <typename T>
struct task_queue_t{
    task_queue_t(int _count=QUEUECOUNT)
    {
        if(_count<=0){
            throw "queue is zero";
        }

        max_count=_count;
        cur_count=0;
        queue=new T[max_count];
        if(queue==nullptr)
        {
            throw bad_alloc();
        }
        head=0;
        tail=0;
    }

    ~task_queue_t()
    {
        if(queue!=nullptr){
            delete queue;
            queue=nullptr;
        }
    }

    size_t head;  //可读任务的位置
    size_t tail;  //可写任务的位置
    size_t max_count;//最大的任务数量
    size_t cur_count;//当前队列中的任务数量
    T* queue;
};

4. Constructor

 

template <typename T>
threadpool<T>::threadpool(size_t _thrd_count,size_t _task_size)
{
    pool=new pthread_t[thrd_count];
    if(pool==nullptr)
    started=0;
    task_queue=new task_queue_t<T*>(_task_size);
    if(task_queue==nullptr)
    {
        throw bad_alloc();
    }

    pthread_mutex_init(&lock,NULL);
    pthread_cond_init(&cond,NULL);

    for(int i=0;i<_thrd_count;i++)
    {
        pthread_create(&pool[i],NULL,routine,this);
        thrd_count++;
        started++;
    }
}

 

5.push interface design

 

template <typename T>
int threadpool<T>::push(T* task){
    if(pool==nullptr||task_queue==nullptr){
        return -1;
    }

    if(pthread_mutex_lock(&lock)==-1){
        return -1;
    }

    if(close){
        pthread_mutex_unlock(&lock);
        return -1;
    }

    if(task_queue->cur_count==task_queue->max_count){
        pthread_mutex_unlock(&lock);
        return -1;
    }

    task_queue->queue[task_queue->tail]=task;
    if(pthread_cond_signal(&cond)!=0){
        pthread_mutex_unlock(&lock);
        return -1;
    }
    
    task_queue->tail=(task_queue->tail+1)%task_queue->max_count;
    task_queue->cur_count++;
    pthread_mutex_unlock(&lock);
    return 0;
}

6. Execution function of child thread


template <typename T>
void* threadpool<T>::routine(void* arg)
{
    threadpool<T>* pool=(threadpool<T>*) arg;

    while(true)
    {
        /*
        1.加锁,进入请求池获取任务
        2.如果没有任务,则阻塞在条件变量中,解锁
        3.如果有任务,取出任务则运行该任务
        */

       pthread_mutex_lock(&pool->lock);

       while(pool->task_queue->cur_count==0&&pool->close==false)
       {
          pthread_cond_wait(&pool->cond,&pool->lock);
       }
       
       if(pool->close==true)break;
       task_queue_t<T*>* queue=pool->task_queue;
       T* t=queue->queue[queue->head];
       pool->task_queue->head=(pool->task_queue->head+1)%pool->task_queue->max_count;
       pool->task_queue->cur_count-=1;//任务数量-1
       pthread_mutex_unlock(&pool->lock);
       t->process();
    }
    //退出线程
    pool->started--;//活跃线程减1
    pthread_mutex_unlock(&pool->lock);
    pthread_exit(NULL);
    return NULL;
}

7. Destructor

template <typename T>
threadpool<T>::~threadpool()
{
    /*
    1.先请求池中的任务全部处理完
    2.在退出所有的线程
    3.释放线程池资源
    */

   if(pool==nullptr){
    return;
   }
  if(pthread_mutex_lock(&lock)<0){
    //加锁失败
     m_errno=2;
     return;
   }
   if(close==true)  
   {
     thread_pool_free();
     return;
   }

   close=true;
   if(pthread_cond_broadcast(&cond)<0||pthread_mutex_unlock(&lock)<0){
      thread_pool_free();
      m_errno=3;
      return;
   }

   wait_all_done();
   thread_pool_free();
   return;
}

template <typename T>
int threadpool<T>::wait_all_done()
{
    int ret=0;
    if(pool==nullptr){
        return 1;
    }

    for(int i=0;i<thrd_count;i++)
    {
        if(pthread_join(pool[i],NULL)!=0){
            ret=1;
        }
    }
    return ret;
}


template <typename T>
int threadpool<T>::thread_pool_free()
{

    if(pool!=nullptr||started>0){
        //还有活跃线程,不能还不能销毁资源
        return -1;
    }

    delete[] pool;
    pool=nullptr;

    if(task_queue!=nullptr)
    {
      delete task_queue;
      task_queue=nullptr;
    }

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);

    started=0;
    thrd_count=0;
}

Why do we need to wait for the child thread to exit before releasing the thread pool resources?

In order to ensure that all tasks in the thread pool can be executed completely, if there are still threads in the thread pool executing, if the main thread suddenly exits, some tasks will not be executed, or the execution will be incomplete.

8. Test the thread pool

The following code tests whether threads in the thread pool can execute tasks concurrently.

  
struct task{
    static  int done ;
    static  pthread_mutex_t lock;
    public:
    void process(){
     usleep(10000);
      pthread_mutex_lock(&lock);
      done++;
      printf("%d:doing %d task\n",pthread_self(), done);
      pthread_mutex_unlock(&lock);
    }
};
int task::done = 0;
pthread_mutex_t task::lock;

int main()
{
    threadpool<task>* pool=new threadpool<task>();
    if (pool == NULL) {
        printf("thread pool create error!\n");
        return 1;
    }

    task* t=new task;
    while (pool->push(t)==0) {
       /// pthread_mutex_lock(&lock);
        nums++;
      //  pthread_mutex_unlock(&lock);
        t=new task;
    }

    printf("add %d tasks\n", nums);
    
    pool->wait_all_done();

   // printf("did %d tasks\n", done);
}

9. Setting the number of threads in the thread pool

The number of threads in the thread pool needs to be balanced with CPU resources.

The number of threads in a thread is not as high as possible because a CPU can only run one thread. The purpose of creating a thread pool is to make full use of CPU resources.

How to reuse cpu resources?

When some threads are blocking and waiting for IO, the CPU resources will not be used at this time. At this time, you can switch to another thread so that the CPU can continue to perform operations.

1.Experience value

Before configuring the number of threads, you first need to check whether the type of task is IO-intensive or CPU-intensive ?

What is IO intensive?
For example: the data on the disk is frequently read, or the interface needs to be called remotely through the network.
What is CPU intensive?
For example: very complex calls, many loops, or deep recursive calls, etc.

If it is a mixed type (including both IO-intensive and CPU-intensive ), how to configure the number of threads?

If the hybrid type is IO-intensive and the execution time of the CPU-intensive type is not very different, it can be separated for better configuration. If the execution time is too different, optimization is of little significance. For example, IO-intensive execution takes 60 seconds, and CPU-intensive execution takes 1 second.

2. Optimum number of threads algorithm

In addition to the empirical values ​​introduced above, the calculation formula is also provided:

Optimal number of threads = ((thread waiting time + thread CPU time) / thread CPU time) * number of CPUs

Because obviously, the higher the proportion of thread waiting time, the more threads are needed. The higher the percentage of thread CPU time, the fewer threads are needed.

Guess you like

Origin blog.csdn.net/sjp11/article/details/132149165