On thread pool

Time:2022-1-7

Thread pool

concept

Thread pool is to create some threads first, and their collection is called thread pool. The use of thread pool can improve the performance. The thread pool creates a large number of idle threads when the system starts. When there are tasks in the task pool, the threads in the thread pool queue up to pick up the tasks of the task pool. If there are no tasks at present, they will block the waiting.

Working mechanism

  1. In the case of wired process pool, the task is handed over to the thread pool rather than directly to a thread. After the thread pool gets the task, it looks for whether there is an idle thread, and if so, it will be delivered to the thread for execution.
  2. A thread can only execute one task at the same time. After execution, the thread returns to idle state and waits for the next task.

Reasons for using thread pools

Multithreading running time, the system constantly starts and closes threads, the cost is very high, it will consume thread resources excessively, and the risk of excessive thread switching, resulting in the collapse of system resources.

Graphical thread pool

On thread pool

code

Thread pool structure

typedef struct NMANAGER
{
    struct NWORKER *workers; //  Managed thread chain header node
    struct NJOBS *jobs;      // Task pool header node

    int totWorkers;  //  How many threads are there currently
    int idleWorkers; //  Current number of idle threads

    pthread_ mutex_ t pool_ mtx; //  Thread pool mutex
    pthread_ cond_ t pool_ cond; //  Thread pool condition lock. All threads wait for this condition lock

} nManager;

Thread structure

typedef struct NWORKER
{
    pthread_ t pthreadid; // The thread ID of the thread
    struct NMANAGER *pool; //  In which thread pool
    int terminate; //  Whether the thread is terminated or not, 1 is to destroy the thread, and 0 is the opposite
    struct NWORKER *next; // Next thread in the linked list
    struct NWORKER *prev;// Previous thread in the linked list
} nWorker;

Task pool

typedef struct NJOBS
{
    void (*func)(void *arg); // Task callback, and the thread executes the task
    void *user_ data;         //  Task parameters

    struct NJOBS *next; // Next task in the task pool
    struct NJOBS *prev; //  Task pool previous task
} nJobs;

Creation of thread pool

int ThreadPoolCreate(threadpool *pool, int numsThread)
{
    //1.  Initialize thread pool related properties
    //2.  Create a certain number of threads and throw them into the thread pool
    if (pool == NULL)
    {
        printf("create error\n");
        return 1;
    }
    if (numsThread <= 0)
        numsThread = 1;
    memset(pool, 0, sizeof(nManager));
    pool->totWorkers = 0;
    pool->idleWorkers = 0;
    pthread_mutex_init(&pool->pool_mtx, NULL);
    pthread_cond_init(&pool->pool_cond, NULL);
    //pool->pool_mtx = PTHREAD_MUTEX_INITIALIZER;
    pthread_t managerpid;
    pthread_create(&managerpid, NULL, ManagerThread, (void *)pool);
    pthread_detach(managerpid);
    int i = 0;
    for (i = 0; i < numsThread; i++)
    {
        nWorker *worker = (nWorker *)malloc(sizeof(nWorker));
        if (worker == NULL)
        {
            printf("worker error\n");
            return 1;
        }
        memset(worker, 0, sizeof(nWorker));
        int ret = pthread_create(&worker->pthreadid, NULL, ThreadWorking, worker);
        if (ret)
        {
            perror("pthread create");
            return 1;
        }
        pthread_detach(worker->pthreadid);
        worker->terminate = 0;
        worker->pool = pool;
        LIST_ ADD(worker, pool->workers); //  Throw into thread pool
        pthread_mutex_lock(&pool->pool_mtx);
        pool->totWorkers++;  //  Total threads increased
        pool->idleWorkers++; //  Idle Thread 
        pthread_mutex_unlock(&pool->pool_mtx);
    }
    return 0;
}

Destroy thread pool

void ThreadPoolDestroy(threadpool *pool)
{
    nWorker *w = pool->workers;
    for (w = pool->workers; w != NULL; w = w->next)
    {
        w->terminate = 1; //  If teminate = 1, the thread will destroy itself in the execution function, and the coupling degree is low.
    }
    pthread_mutex_lock(&pool->pool_mtx);
    pthread_ cond_ broadcast(&pool->pool_cond); //  Notify all threads waiting for conditional locks. Threads that do not wait will destroy themselves after completing tasks
    pthread_mutex_unlock(&pool->pool_mtx);
    free(pool);
}

Add task to thread pool

void ThreadPushJob(threadpool *pool, nJobs *job)
{
    pthread_mutex_lock(&pool->pool_mtx);
    LIST_ ADD(job, pool->jobs); //  Add task to thread pool
    printf("add jobs in pool\n");
    pthread_ cond_ signal(&pool->pool_cond); //  Notify a thread of the task
    pthread_mutex_unlock(&pool->pool_mtx);
}

Thread execution function

void *ThreadWorking(void *arg)
{
    nWorker *worker = (nWorker *)arg;
    while (1)
    {
        pthread_mutex_lock(&worker->pool->pool_mtx);
        while (worker->pool->jobs == NULL)
        {
            printf("work waiting------\n");
            if (worker->terminate == 1)
            {
                //The thread was destroyed
                break;
            }
            pthread_ cond_ wait(&worker->pool->pool_cond, &worker->pool->pool_mtx); // When there are tasks in the task pool,
                                                                                  //Unlock before the function, block on the condition variable, and add the corresponding mutex after the function
        }
        if (worker->terminate == 1)
        {
            pthread_mutex_unlock(&worker->pool->pool_mtx);
            break;
        }
        nJobs *jobs = worker->pool->jobs;
        LIST_ DEL(jobs, worker->pool->jobs);            // Remove the task from the task pool
        pthread_ mutex_ unlock(&worker->pool->pool_mtx); //  Now allow other threads to fetch tasks from the task pool
        pthread_mutex_lock(&worker->pool->pool_mtx);
        worker->pool->idleWorkers--; //  Idle thread minus one
        pthread_mutex_unlock(&worker->pool->pool_mtx);
        printf("working -------\n");
        jobs->func(jobs->user_data); // Callback function to execute the task
        pthread_mutex_lock(&worker->pool->pool_mtx);
        worker->pool->idleWorkers++; // Idle threads plus one
        pthread_mutex_unlock(&worker->pool->pool_mtx);
        free(jobs); //  Free up memory for this task
    }
    //Destroy the thread and modify the information related to the thread
    pthread_mutex_lock(&worker->pool->pool_mtx);
    worker->pool->totWorkers--;
    worker->pool->idleWorkers--;
    LIST_ DEL(worker, worker->pool->workers); //  Remove from thread pool
    pthread_mutex_unlock(&worker->pool->pool_mtx);
    free(worker); //  release
    pthread_exit(NULL);
}

Remaining questions

  • At present, thread pool does not support dynamic reduction and expansion. Generally, when there are too many idle threads, we consider reducing the thread pool capacity. When there are too many busy threads, we consider increasing threads.
  • The structure of the thread pool already has a total number of threads and leisure threads, which can be used to write corresponding programs.
  • For the size of thread pool, we can use a thread: manager thread to maintain the size of thread pool

Recommended Today

Proper memory alignment in go language

problem type Part1 struct { a bool b int32 c int8 d int64 e byte } Before we start, I want you to calculatePart1What is the total occupancy size? func main() { fmt.Printf(“bool size: %d\n”, unsafe.Sizeof(bool(true))) fmt.Printf(“int32 size: %d\n”, unsafe.Sizeof(int32(0))) fmt.Printf(“int8 size: %d\n”, unsafe.Sizeof(int8(0))) fmt.Printf(“int64 size: %d\n”, unsafe.Sizeof(int64(0))) fmt.Printf(“byte size: %d\n”, unsafe.Sizeof(byte(0))) fmt.Printf(“string size: %d\n”, […]