Principle, design and implementation of thread pool

Posted by sharpmac on Sun, 16 Jan 2022 05:19:29 +0100

Principle and implementation of thread pool

1. What is a thread pool

Thread pool is a form of multi-threaded processing. Threads are created in advance and placed in a queue for management. When a task needs to be processed, the task is assigned to a specific thread for execution. Improve performance by reducing thread creation, destruction, and switching.

2. Thread pool principle

A large number of tasks need to be processed, and multithreading will certainly be considered. Usually a task directly calls a thread to execute, which is inefficient. Therefore, there is a thread pool. Quantitative threads are created in advance, waiting for tasks in the queue, and specific threads are assigned to execute tasks when there are tasks. Callback functions and parameters are submitted to threads in the form of tasks, avoiding the creation and destruction of threads and reducing thread switching. Since it is quantitative, how much is this quantity.

3. Thread pool I designed

3.1 data structure

I use a task queue (one-way linked list) and two thread queues (two-way linked list): idle queue and busy queue.

  • Task node: it stores each specific task information
    typedef struct _TaskNode {
    struct _ThreadPool *thread_pool;
    void *args; // fun arg
    void *(working)(void); // the real work of the task
    int task_id; // task id
    struct _TaskNode *next;
    }TaskNode;
  1. Pointer to ThreadPool to point to global variables from the task node.
  2. Parameters of the task (function) to be completed
  3. Tasks to be completed (address of function)
  4. Task id number
  5. Point to next task
  • Task queue: it stores the information of the whole task queue
    typedef struct _TaskQueue {
    pthread_mutex_t mutex;
    // when no task, the manager thread wait for; when a new task come, signal
    pthread_cond_t cond;
    struct _TaskNode *head; // point to the task_link head
    struct _TaskNode *rear; // point to the task_link rear
    // current number of task, include unassigned and assigned but no finished
    int number;
    // task id initialize 0, add 1 when add task to task link
    int task_id;
    }TaskQueue;
  1. The task queue is mutually exclusive. It needs to be used when adding and removing tasks
  2. If the task queue condition locks, the management thread will wait for the task, and the management thread needs to be notified when adding a task
  3. Head node of task queue
  4. Tail node of task queue
  5. Total tasks of task queue
  6. Current task number, used for the next task assignment id
  • Thread node: save the information required by each thread
    typedef struct _PthreadNode {
    // the pid of this thread in kernel, the value is syscall return
    pthread_t tid;
    struct _ThreadPool *thread_pool;
    struct _TaskNode *work; // if exec a work, which work
    struct _PthreadNode *next;
    struct _PthreadNode *prev;
    pthread_cond_t cond; // when assigned a task, signal this child thread by manager.
    pthread_mutex_t mutex;
    }PthreadNode;
  1. Thread id number, delete thread, wait thread and facilitate debugging
  2. Pointer to ThreadPool to point to global variables from the task node.
  3. Pointer to the task node. If it is empty, the thread will be idle; otherwise, the current task will be executed
  4. Point to the next thread node
  5. Point to previous thread node
  6. The conditional lock of the thread is used to manage the thread to wake up the current thread after allocating tasks
  7. Mutex of threads for conditional locks
  • Thread queue: stores all the information of thread queue
    typedef struct _PthreadQueue {
    int number; // the number of thread in this queue.
    struct _PthreadNode *head;
    struct _PthreadNode *rear;
    // when no idle thread, the manager wait for, or when a thread return with idle, signal
    pthread_cond_t cond;
    pthread_mutex_t mutex;
    }PthreadQueue;
  1. Number of threads in the thread queue
  2. Head node of thread queue
  3. Tail node of thread queue
  4. The conditional lock of thread queue is used to manage threads waiting for idle threads.
  5. Mutex of thread queue, used to delete threads and add threads.
  • Information: store information conducive to debugging and the trend of the whole program.
    typedef struct _Info {
    FILE *info_fd;
    struct timeval time_begin;
    pthread_mutex_t mutex_info;
    }Info;
  1. Information store file handle
  2. Program start time
  3. Information mutex to avoid multiple threads writing files at the same time
  • Global variables: encapsulate all global variables into the structure, and multiple thread pools can be created at the same time
    typedef struct _ThreadPool {
    PthreadQueue *pthread_queue_idle; // the idle thread double link queue.
    PthreadQueue *pthread_queue_busy; // the work thread double link queue.
    TaskQueue *task_queue_head; // the task queue single link list
    int pthread_current_count; // the count of current pthread
    Info info;
    } ThreadPool;
  1. Idle queue pointer
  2. Busy queue pointer
  3. Task queue pointer
  4. Total threads
  5. information

3.2 design of dynamic thread number

I designed the number of thread pools into a dynamic form. The default number is 8 threads.

  • If it reaches the growth point (the thread occupancy rate reaches 1, and all threads are busy) and is less than the maximum number of threads (I set it to 1024), it will increase the number of threads by 25% (new number = current number of threads * 25%; if it is greater than the maximum number of threads, it = maximum number of threads - current number of threads), and insert the new threads into the tail of the idle queue in turn.
  • When the deletion point is reached (the thread occupancy rate reaches 0.6, the number of worker threads / bus threads) and is greater than the minimum number of threads (I set it to 8), delete 30% of the threads (deletion number = current threads * 30%, if it is less than the minimum number of threads, then = current threads - Minimum threads), and delete the corresponding number at the end of the idle queue.
    Delete pthread used by thread_ After cancel () calls this function, I call pthread again_ Join() to wait for the end of the thread to be deleted, Then release the memory (spit out the core and find out the reason for it for a long time. In the process of dynamic deletion, it happens that a thread has just finished executing the task and found that there is no task. Insert itself into the tail of the idle queue and release the lock of the idle queue. Then the monitoring thread gets the lock and executes the deletion thread. pthread_cancel() will not end the process until the cancellation point or blocking point, After deleting the thread and freing the space, the thread to be deleted is still executing and then the core is released when the space is released.

3.3 specific operation process

  • External interfaces: InitSystem (initialize thread pool) and ThreadPoolRequireTask (request task).
  • Info: in the case of debug, add - define info during compilation_ Flag can open the information and record the call of each thread, the processing of tasks, and the addition and deletion of management threads. The management thread can view a series of local variables written into the information within 0.5 seconds.
  • InitSystem (initialization thread), initialize all global variables and call CreatePthreadPool to create the default number of idle threads, and create PthreadManager (management thread) and Monitor (monitoring thread).
  • ThreadPoolRequireTask (request task), pass in the global variable, the parameters of the callback function and the callback function, create a new task and join the task queue, release the lock of the task queue and re lock the task queue (if a busy thread just finished executing the task, you can get the task to reduce thread switching when you check whether there is a task again), Notify the management thread if there are tasks.
  • CreatePthreadPool is used to create threads, pass in global variables and the number of created threads, add the created threads to idle threads, and the thread execution function is ChildWork.
  • ChildWork (thread execution function), internal dead loop: if there is no work, wait for its own conditional lock signal (management thread wakes up), if there is work, execute the work, and then release the parameters of the callback function and the completed task. Check whether there are tasks in the task queue. If there are tasks, take a task to cycle again. If there are no tasks, add the thread from the busy queue to the end of the idle queue to continue the cycle.
  • PthreadManager (management thread), wait for idle threads in the idle queue, take out the first idle thread and continuously open the connection to the idle queue. The first task may be fetched out of the waiting queue by other threads (the first task may be fetched out of the waiting queue). Disconnect the fetch thread and notify the thread.
  • Monitor, if info is defined_ Flag checks the number of idle threads, the number of busy threads, the total number of threads, thread occupancy, the number of pending tasks and the current task id every 0.5 seconds and writes them into the information. If the thread growth conditions are met, a thread is created. Otherwise, judge whether the deletion conditions are met, and if so, delete the thread.

4. Code implementation

pthread_pool.h

/******************************************************************************
*
*  Copyright (C), 2001-2005, Huawei Tech. Co., Ltd.
*
*******************************************************************************
*  File Name     : pthread_pool.h
*  Version       : Initial Draft
*  Author        : sst
*  Created       : 2021/3/28
*  Last Modified :
*  Description   : pthread_pool.c header file
*  Function List :
*
*
*  History:
* 
*       1.  Date         : 2021/3/28
*           Author       : sst
*           Modification : Created file
*
******************************************************************************/
#ifndef __PTHREAD_POOL_H__
#define __PTHREAD_POOL_H__


#ifdef __cplusplus
#if __cplusplus
extern "C"{
#endif
#endif /* __cplusplus */


/*==============================================*
 *      include header files                    *
 *----------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/sem.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <unistd.h>

/*==============================================*
 *      constants or macros define              *
 *----------------------------------------------*/
#define THREAD_MAX_NUM 1024     // max number of thread pool
#define THREAD_DEF_NUM 8        // by default, number of thread
#define THREAD_MIN_NUM 8        // min number of thread pool
#define THREAD_ADD_POINT 1.0    // %, malloc new thread when usage rate greater then it
#define THREAD_DEL_POINT 0.6    // %, free idle thread when usage rate less then it
#define THREAD_ADD_RATE 0.25    // %, malloc new thread = sum thread * it
#define THREAD_DEL_RATE 0.3     // %, free idle thread = sum thread * it
#define INFO_MAX 1024
/*==============================================*
 *      project-wide global variables           *
 *----------------------------------------------*/
/*==============================================*
 *      routines' or functions' implementations *
 *----------------------------------------------*/ 
struct _ThreadPool;
// ds of the every task. make all task in a single link
typedef struct _TaskNode {
    struct _ThreadPool *thread_pool;
    void *args;                 // fun arg
    void *(*working)(void*);    // the real work of the task
    int task_id; // task id
    struct _TaskNode *next;
}TaskNode;

// the ds of the task_queue
typedef struct _TaskQueue {
    pthread_mutex_t mutex;
    // when no task, the manager thread wait for; when a new task come, signal
    pthread_cond_t cond;
    struct _TaskNode *head;     // point to the task_link head
    struct _TaskNode *rear;     // point to the task_link rear
    // current number of task, include unassigned and assigned but no finished
    int number;
    // task id initialize 0, add 1 when add task to task link
    int task_id;
}TaskQueue;

// the ds of every thread, mask all thread in a double link queue.
typedef struct _PthreadNode {
    // the pid of this thread in kernel, the value is syscall return
    pthread_t tid;          
    struct _ThreadPool *thread_pool;
    struct _TaskNode *work; // if exec a work, which work
    struct _PthreadNode *next;
    struct _PthreadNode *prev;
    pthread_cond_t cond;    // when assigned a task, signal this child thread by manager.
    pthread_mutex_t mutex;
}PthreadNode;

// the ds of the thread queue
typedef struct _PthreadQueue {
    int number;             // the number of thread in this queue.
    struct _PthreadNode *head;
    struct _PthreadNode *rear;
    // when no idle thread, the manager wait for, or when a thread return with idle, signal
    pthread_cond_t cond;
    pthread_mutex_t mutex;
}PthreadQueue;

typedef struct _Info {
    FILE *info_fd;
    struct timeval time_begin;
    pthread_mutex_t mutex_info;
}Info;

typedef struct _ThreadPool {

   PthreadQueue *pthread_queue_idle;     // the idle thread double link queue.
    PthreadQueue *pthread_queue_busy;     // the work thread double link queue.
    TaskQueue *task_queue_head;           // the task queue single link list
    int pthread_current_count;            // the count of current pthread
    Info info;
} ThreadPool;

extern void* ChildWork(void *args);
extern void CloseInfo(Info *info);
extern void CreatePthreadPool
(ThreadPool *thread_pool, const int count);
extern void InitSystem(ThreadPool *thread_pool);
extern void* Monitor(void *args);
extern void OpenInfo(Info *info, const char *info_path);
extern void* PthreadManager(void *args);
extern void PthreadQueueAdd(PthreadQueue *pthread_queue,
                              PthreadNode* pthread_node);
extern void PthreadQueueUnlink(PthreadQueue* pthread_queue, 
                                 PthreadNode* pthread_node);
extern void TaskAddNew(TaskNode *new_task);
extern void TaskGetNew(PthreadNode *pthread_node);
extern void ThreadIdleDel(ThreadPool *thread_pool, int count);
extern int ThreadPoolRequireTask(ThreadPool *thread_pool, void *args, 
                          void *(*working)(void *args));
extern void TimeCurrentSpend(struct timeval *time_old, char *info);
extern void WriteInfo(Info *info, char *buffer);


#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */


#endif /* __PTHREAD_POOL_H__ */

pthread_pool.c

#include "pthread_pool.h"

void OpenInfo(Info *info, const char *info_path) {
    info->info_fd = fopen(info_path, "w");
}

// lock log and write log
void WriteInfo(Info *info, char *buffer) {
    TimeCurrentSpend(&info->time_begin, buffer);
    pthread_mutex_lock(&info->mutex_info);
    fwrite(buffer, strlen(buffer), 1, info->info_fd);
    fflush(info->info_fd);
    pthread_mutex_unlock(&info->mutex_info);
}

void CloseInfo(Info *info) {
    fclose(info->info_fd);
}

// spend time = current time - old time
// day + hours + minute + second + millisecond + mincrosecond + info
void TimeCurrentSpend(struct timeval *time_old, char *info)
{
    struct timeval now;
    gettimeofday(&now, NULL);
    if(now.tv_usec < time_old->tv_usec)
    {
        now.tv_sec--;
        now.tv_usec += 1000000;
    }
    time_t sec = now.tv_sec - time_old->tv_sec;
    char info_old[strlen(info) + 1];
    strcpy(info_old, info);
    sprintf(info, "%2ldd,%2ldh,%2ldm,%2lds,%4ldms,%4ldus :  %s",
        sec /8640,                                  // day
        (sec / 360) % 24,                           // hours
        (sec / 60) % 60,                            // minute
        sec % 60,                                   // second
        (now.tv_usec - time_old->tv_usec) / 1000,    // millisecond
        (now.tv_usec - time_old->tv_usec) % 1000,    // mincrosecond
        info_old);                                  // info
}

// must malloc args memory and not must free
int ThreadPoolRequireTask(ThreadPool *thread_pool, void *args, 
                          void *(*working)(void *args)) {
    TaskNode *new_task = (TaskNode*)malloc(sizeof(TaskNode));
    if (new_task == NULL) {
        perror("ThreadPoolRequireTask malloc failure ");
        return -1;
    }
    new_task->thread_pool = thread_pool;
    new_task->args = args;
    new_task->working = working;
    new_task->next = NULL;

    TaskQueue *task_queue_head = new_task->thread_pool->task_queue_head;
    TaskAddNew(new_task);

#ifdef INFO_FLAG
    pthread_t tid = syscall(SYS_gettid);
    char info_buf[INFO_MAX];
    sprintf(info_buf, "task_manager : %ld, add task id : %d\n", 
                      tid, new_task->task_id);
    WriteInfo(&new_task->thread_pool->info, info_buf);
#endif

    // again require mutex lock for reduce thread status change
    pthread_mutex_lock(&task_queue_head->mutex);
    if (task_queue_head->number) {
        pthread_cond_signal(&task_queue_head->cond);
    }
    pthread_mutex_unlock(&task_queue_head->mutex);
    
    return 0;
}

// need lock PthreadNode and TaskQueue at call
void TaskGetNew(PthreadNode *pthread_node) {
    TaskQueue *task_queue_head = pthread_node->thread_pool->task_queue_head;
    pthread_node->work = task_queue_head->head;
    // get the first task and modify self thread attribute
    if (task_queue_head->number == 1) {
        task_queue_head->head = task_queue_head->rear = NULL;
    } else {
        task_queue_head->head = task_queue_head->head->next;
    }
    task_queue_head->number--;
    pthread_node->work->next = NULL;
}

void TaskAddNew(TaskNode *new_task) {
    /*
        initial the attribute of the task.
        because this task havn't add to system, so no need lock the mutex.
    */
    TaskQueue *task_queue_head = new_task->thread_pool->task_queue_head;
    // add the set task node to task link
    pthread_mutex_lock(&task_queue_head->mutex);
    new_task->task_id = ++task_queue_head->task_id;
    // find the tail of the task link and add the new one to tail
    if(!task_queue_head->number) {
        task_queue_head->head = task_queue_head->rear = new_task;
    } else {
        task_queue_head->rear->next = new_task;
        task_queue_head->rear = new_task;
    }
    task_queue_head->number++;
    pthread_mutex_unlock(&task_queue_head->mutex);
}

// delete pthread node from pthread queue but don't destory
void PthreadQueueUnlink(PthreadQueue* pthread_queue, 
                        PthreadNode* pthread_node) {
    // no task need to exec, add self to idle queue and del from busy queue
    // self is the last execute thread
    pthread_mutex_lock(&pthread_queue->mutex);
    if(pthread_queue->head == pthread_node
    && pthread_queue->rear == pthread_node) {
        pthread_queue->head = pthread_queue->rear = NULL;
    } else if(pthread_queue->head == pthread_node
           && pthread_queue->rear != pthread_node) {
        // the first one thread in pthread queue
        pthread_queue->head = pthread_queue->head->next;
        pthread_queue->head->prev = NULL;
    } else if(pthread_queue->head != pthread_node
           && pthread_queue->rear == pthread_node) {
        // the last one thread in pthread queue
        pthread_queue->rear = pthread_queue->rear->prev;
        pthread_queue->rear->next = NULL;
    } else {
        // middle one
        pthread_node->next->prev = pthread_node->prev;
        pthread_node->prev->next = pthread_node->next;
    }
    pthread_queue->number--;
    pthread_node->next = pthread_node->prev = NULL;
    pthread_mutex_unlock(&pthread_queue->mutex);
}

// add PthreadNode to PthreadQueue head
void PthreadQueueAdd(PthreadQueue *pthread_queue,
                     PthreadNode* pthread_node) {
    // now the idle queue is empty
    pthread_mutex_lock(&pthread_queue->mutex);
    if(pthread_queue->head == NULL &&
       pthread_queue->rear == NULL) {
        pthread_queue->head = pthread_queue->rear = pthread_node;
        pthread_node->next = pthread_node->prev = NULL;
    } else {
        pthread_queue->rear->next = pthread_node;
        pthread_node->next = NULL;
        pthread_node->prev = pthread_queue->rear;
        pthread_queue->rear = pthread_node;
    }
    pthread_queue->number++;
    pthread_mutex_unlock(&pthread_queue->mutex);
}
/*
    child_work : the code exec in child thread
    ptr : the ds of thread_node of current thread.
    return : nothing. void* just avoid warning.
*/
void* ChildWork(void *args) {
    // restore the parameters
    PthreadNode *self = (PthreadNode*)args;
    ThreadPool *thread_pool = self->thread_pool;
    // write log
#ifdef INFO_FLAG
    char info_buf[INFO_MAX];
    sprintf(info_buf, "%ld thread prepare\n", self->tid);
    WriteInfo(&thread_pool->info, info_buf);
#endif
    
    while(1) {
        // if no task exec, blocked
        pthread_mutex_lock(&self->mutex);
        if(self->work == NULL) {
            pthread_cond_wait(&self->cond, &self->mutex);
        }
        pthread_mutex_unlock(&self->mutex);
        
#ifdef INFO_FLAG
        sprintf(info_buf, "%ld thread working, task id is %d\n", 
                                self->tid, self->work->task_id);
        WriteInfo(&thread_pool->info, info_buf);
#endif
        // execute the real work and destroy
        self->work->working(self->work->args);
        // destroy the task space
        free(self->work->args);
        args = NULL;
        self->work->working = NULL;
        self->work->next = NULL;
        free(self->work);
        self->work = NULL;
        /*
            get new task from the task_link if not NULL.
            there are no idle thread if there are task to do.
            if there are not task, mask self idle and add to the idle queue.
        */
        pthread_mutex_lock(&thread_pool->task_queue_head->mutex);
        if(thread_pool->task_queue_head->number > 0) {
            TaskGetNew(self);
            pthread_mutex_unlock(&thread_pool->task_queue_head->mutex);
            continue;
        } else {

            pthread_mutex_unlock(&thread_pool->task_queue_head->mutex);
            
            PthreadQueueUnlink(thread_pool->pthread_queue_busy, self);
            PthreadQueueAdd(thread_pool->pthread_queue_idle, self);

            pthread_mutex_lock(&thread_pool->pthread_queue_idle->mutex);
            pthread_cond_signal(&thread_pool->pthread_queue_idle->cond);
            pthread_mutex_unlock(&thread_pool->pthread_queue_idle->mutex);            
        }
    }
    return NULL;
}

/*
    create thread pool when the system on, and thread number is THREAD_DEF_NUM.
    when niit, initial all the thread into a double link queue and all wait for self->cond.
*/
void CreatePthreadPool
(ThreadPool *thread_pool, const int count) {
    // init as a double link queue
    int i;
    PthreadNode *temp, *head = NULL, *prev = NULL;
    for(i = 0; i < count; ++i) {
        temp = (PthreadNode*)malloc(sizeof(PthreadNode));
        if (head == NULL) head = temp;
        if(temp == NULL) {
            perror("malloc failure ");
            exit(1);
        }
    
        temp->thread_pool = thread_pool;
        temp->work = NULL;
        pthread_cond_init(&temp->cond, NULL);
        pthread_mutex_init(&temp->mutex, NULL);

        temp->prev = prev;
        if(prev != NULL) {
            prev->next = temp;
        }
        
        // create this thread
        pthread_create(&temp->tid, NULL, ChildWork, (void*)temp);
        prev = temp;
    }
    temp->next = NULL;
    
    // modify the idle thread queue attribute
    pthread_mutex_lock(&thread_pool->pthread_queue_idle->mutex);
    thread_pool->pthread_current_count += count;
    thread_pool->pthread_queue_idle->number += count;
    if (thread_pool->pthread_queue_idle->head == NULL) {
        thread_pool->pthread_queue_idle->head = head;
        thread_pool->pthread_queue_idle->rear = temp;
    } else {
        thread_pool->pthread_queue_idle->rear->next = head;
    }
    pthread_mutex_unlock(&thread_pool->pthread_queue_idle->mutex);
}

// init_system : init the system glob pointor.
void InitSystem(ThreadPool *thread_pool) {
#ifdef INFO_FLAG
    pthread_mutex_init(&thread_pool->info.mutex_info, NULL);

    gettimeofday(&thread_pool->info.time_begin, NULL);
    OpenInfo(&thread_pool->info, "infomation.txt");
#endif
    thread_pool->pthread_current_count = 0;

    // init the pthread_queue_idle
    thread_pool->pthread_queue_idle = (PthreadQueue*)malloc(sizeof(PthreadQueue));
    thread_pool->pthread_queue_idle->number = 0;
    thread_pool->pthread_queue_idle->head = NULL;
    thread_pool->pthread_queue_idle->rear = NULL;
    pthread_mutex_init(&thread_pool->pthread_queue_idle->mutex, NULL);
    pthread_cond_init(&thread_pool->pthread_queue_idle->cond, NULL);

    // init the pthread_queue_busy
    thread_pool->pthread_queue_busy = (PthreadQueue*)malloc(sizeof(PthreadQueue));
    thread_pool->pthread_queue_busy->number = 0;
    thread_pool->pthread_queue_busy->head = NULL;
    thread_pool->pthread_queue_busy->rear = NULL;
    pthread_mutex_init(&thread_pool->pthread_queue_busy->mutex, NULL);
    pthread_cond_init(&thread_pool->pthread_queue_busy->cond, NULL);

    // init the task_queue_head
    thread_pool->task_queue_head = (TaskQueue*)malloc(sizeof(TaskQueue));
    thread_pool->task_queue_head->head = NULL;
    thread_pool->task_queue_head->number = 0;
    thread_pool->task_queue_head->task_id = 0;
    pthread_mutex_init(&thread_pool->task_queue_head->mutex, NULL);

    pthread_cond_init(&thread_pool->task_queue_head->cond, NULL);

    // create thread pool
    CreatePthreadPool(thread_pool, THREAD_DEF_NUM);

    // create thread of manage
    pthread_t thread_manage_id;
    pthread_create(&thread_manage_id, NULL, PthreadManager, (void*)thread_pool);

    // create thread of manage
    pthread_t thread_monitor_id;
    pthread_create(&thread_monitor_id, NULL, Monitor, (void*)thread_pool);
}

/*
    PthreadManage : allocate tasks to idle threads.
            block on pthread_queue_idle->cond when no idle thread
            block on task_queue_head->cond when no task come.
    ptr : no used, in order to avoid warning.
    return : nothing.
*/
void* PthreadManager(void *args)
{
    ThreadPool *thread_pool = (ThreadPool*)args;
#ifdef INFO_FLAG
    pthread_t tid = syscall(SYS_gettid);
    char info_buf[INFO_MAX];
    sprintf(info_buf, "PthreadManager : %ld\n", tid);
    WriteInfo(&thread_pool->info, info_buf);
#endif

    while(1)
    {
        PthreadNode *temp_thread = NULL;
        
        // get a new idle thread, and modify the idle_queue.
        // if no idle thread, block on pthread_queue_idle->cond.
        pthread_mutex_lock(&thread_pool->pthread_queue_idle->mutex);
        if(thread_pool->pthread_queue_idle->number == 0) {
            pthread_cond_wait(&thread_pool->pthread_queue_idle->cond, 
                              &thread_pool->pthread_queue_idle->mutex);
        }
        temp_thread = thread_pool->pthread_queue_idle->head;
        pthread_mutex_unlock(&thread_pool->pthread_queue_idle->mutex);

        // get a new task, and modify the task_queue.
        // if no task block on task_queue_head->cond.
        pthread_mutex_lock(&thread_pool->task_queue_head->mutex);
        while(thread_pool->task_queue_head->number == 0) {
            pthread_cond_wait(&thread_pool->task_queue_head->cond, 
                              &thread_pool->task_queue_head->mutex);
        }
        TaskGetNew(temp_thread);
        pthread_mutex_unlock(&thread_pool->task_queue_head->mutex);

        // modify the idle thread attribute.
        // PthreadNode of pthread_queue_idle only allow access to PthreadManage
        // so not have to lock temp_thread
        PthreadQueueUnlink(thread_pool->pthread_queue_idle, temp_thread);
        PthreadQueueAdd(thread_pool->pthread_queue_busy, temp_thread);
#ifdef INFO_FLAG
        sprintf(info_buf, "%ld manager thread : %ld tid, %d work_id\n", 
        tid, temp_thread->tid, temp_thread->work->task_id);
        WriteInfo(&thread_pool->info, info_buf);
#endif
        // signal the child thread to exec the work
        pthread_mutex_lock(&temp_thread->mutex);
        pthread_cond_signal(&temp_thread->cond);
        pthread_mutex_unlock(&temp_thread->mutex);
    }
}

/*
    monitor : get the system info
    ptr : not used, only to avoid warning for pthread_create
    return : nothing
*/
void* Monitor(void *args)
{
    ThreadPool *thread_pool = (ThreadPool*)args;
#ifdef INFO_FLAG
    char info_buf[INFO_MAX];
    sprintf(info_buf, "monitor : %ld\n", syscall(SYS_gettid));
    WriteInfo(&thread_pool->info, info_buf);
#endif

    int idle_num, busy_num, task_num, task_id;
    while(1) {
        idle_num = thread_pool->pthread_queue_idle->number;
        busy_num = thread_pool->pthread_queue_busy->number;
        task_num = thread_pool->task_queue_head->number;
        task_id = thread_pool->task_queue_head->task_id;       
        double usage_rate = (1.0 * busy_num) / (thread_pool->pthread_current_count);
#ifdef INFO_FLAG
        sprintf(info_buf, "monitor : %ld\n", syscall(SYS_gettid));
        sprintf(info_buf, "%s********************************\n", info_buf);
        sprintf(info_buf, "%sidle number : %d\n", info_buf, idle_num);
        sprintf(info_buf, "%sbusy number : %d\n", info_buf, busy_num);
        sprintf(info_buf, "%sthread sum : %d\n", info_buf, thread_pool->pthread_current_count);
        sprintf(info_buf, "%susage rate : %lf\n", info_buf, usage_rate);
        sprintf(info_buf, "%stask number : %d\n", info_buf, task_num);
        sprintf(info_buf, "%stask id : %d\n", info_buf, task_id);
        sprintf(info_buf, "%s ******************************\n\n", info_buf);
        WriteInfo(&thread_pool->info, info_buf);
#endif

        // dynamic manage thread count
        if (usage_rate >= THREAD_ADD_POINT && 
            thread_pool->pthread_current_count < THREAD_MAX_NUM) {
            int count = thread_pool->pthread_current_count * THREAD_ADD_RATE;
            if(thread_pool->pthread_current_count + count > THREAD_MAX_NUM) 
                count = THREAD_MAX_NUM - thread_pool->pthread_current_count;
#ifdef INFO_FLAG
            sprintf(info_buf, "monitor : %ld, create pthread pool %d\n", 
                              syscall(SYS_gettid), count);
            WriteInfo(&thread_pool->info, info_buf);
#endif
            CreatePthreadPool(thread_pool, count);

            pthread_mutex_lock(&thread_pool->pthread_queue_idle->mutex);
            pthread_cond_signal(&thread_pool->pthread_queue_idle->cond);
            pthread_mutex_unlock(&thread_pool->pthread_queue_idle->mutex);
        } else if (usage_rate <= THREAD_DEL_POINT && 
                   thread_pool->pthread_current_count > THREAD_MIN_NUM) {
            int count = thread_pool->pthread_current_count * THREAD_DEL_RATE;
            if(thread_pool->pthread_current_count - count < THREAD_MIN_NUM) 
                count = thread_pool->pthread_current_count - THREAD_MIN_NUM;
#ifdef INFO_FLAG
            sprintf(info_buf, "monitor : %ld, thread idle delete %d\n", 
                               syscall(SYS_gettid), count);
            WriteInfo(&thread_pool->info, info_buf);
#endif            
            ThreadIdleDel(thread_pool, count);
        }

        usleep(500000);
    }
    return NULL;
}

void ThreadIdleDel(ThreadPool *thread_pool, const int count) {
    PthreadNode *thread_del, *thread_tmp = NULL;
    int i, end = thread_pool->pthread_queue_idle->number - count;
    pthread_mutex_lock(&thread_pool->pthread_queue_idle->mutex);
    thread_del = thread_pool->pthread_queue_idle->head;
    for (i = 0; i < end; i++) {
        thread_del = thread_del->next;
    }
    thread_pool->pthread_queue_idle->rear = thread_del->prev;
    thread_del->prev->next = NULL;
    thread_del->prev = NULL;
    thread_pool->pthread_current_count -= count;
    thread_pool->pthread_queue_idle->number -= count;
    pthread_mutex_unlock(&thread_pool->pthread_queue_idle->mutex); 
    
    for (i = 0; i < count; i++) {
        thread_tmp = thread_del;
        thread_del = thread_del->next;

        pthread_cancel(thread_tmp->tid);
        pthread_join(thread_tmp->tid, NULL);
        pthread_mutex_destroy(&thread_tmp->mutex);
        pthread_cond_destroy(&thread_tmp->cond);
        free(thread_tmp);
        thread_tmp = NULL;
    }
    
}

test_project.c

#include "lib/pthread_pool.h"

#include <stdio.h>
#include <string.h>

#include <arpa/inet.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <unistd.h>

int SockCreate()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1) {
        perror("socket error ");
    }
    return sockfd;
}

int SockConnect(int fd, struct sockaddr_in *addr) {
    int res = connect(fd, (struct sockaddr*)addr, 
                      sizeof(struct sockaddr_in));
    if(res < 0) {
        perror("connect failure ");
    }
    return res;
}

void *SendInfo(void *args) {
    struct sockaddr_in *addr = (struct sockaddr_in*)args;

    int sockfd = SockCreate();
    if (sockfd == -1) return NULL;
    if (SockConnect(sockfd, addr) < 0) return NULL;

    char buffer[1024] = "hello world!\n";
    send(sockfd, buffer, strlen(buffer), 0);
    close(sockfd);
    return NULL;
}

int main(int argc, char **argv)
{
    if (argc < 3) {
        printf("./sortware ip port\n");
        exit(errno);
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);

    ThreadPool thread_pool;
    InitSystem(&thread_pool);
    
    int i, size = sizeof(struct sockaddr_in);
    while(1) {
        for(i = 0; i < 100; i++) {
            struct sockaddr_in *addr = (struct sockaddr_in*)malloc(size);
            addr->sin_family = AF_INET;
            addr->sin_addr.s_addr = inet_addr(ip);
            addr->sin_port = htons(port);
            ThreadPoolRequireTask(&thread_pool, (void*)addr, SendInfo);
        }
        char in = getchar();
        if(in == 'q') break;
    }
    return 0;
}

Topics: C C++ Linux