linux C 线程池

参考:linux C 线程池
链接:https://blog.csdn.net/llzhang_fly/article/details/116140236

目录

  • 线程池是什么
  • 为什么要使用线程池
  • 线程池基本原理
  • 一个线程池主要包括以下几个组成部分
  • 应用实例
  • 其他博文

线程池是什么

线程池就好比水龙头的水流入一个较大的水池,当需要水的时候,5个人挑着扁担,每个人同时可以去打10桶水(各自打各自的2通水,互不影响),然后挑着就走。如果没有水池,那么每个人都需要在从水龙头上接自己的2桶水,谁先到水就先打水,这样打水的效率极低,5个人都需要排队打10桶水。

所以:线程池,顾名思义就是由多个线程组成的“水池”,当有任务需要执行时,由空闲线程进行处理,若没有任务,则线程处于休眠等待的状态。

为什么要使用线程池

如果一个应用程序需要频繁创建、销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了;

如果线程创建和销毁时间相比任务执行时间可以忽略不计,则没有必要使用线程池。

比如线程池有3个线程,那调用线程池的程序中,至少会有几个线程呢?

至少会有4个线程,因为主进程也是一个线程。

线程池基本原理

在传统服务器结构中,常是有一个总的监听线程(listen函数)监听有没有新的用户连接服务器, 每当有一个新用户进入,服务器就开启一个新的线程用户处理这个用户的数据包。这个线程只服务于这个用户,当用户与服务器端关闭连接以后,服务器端销毁这个线程。然而频繁地开辟与销毁线程极大地占用了系统的资源。而且在大量用户的情况下,系统为了开辟和销毁线程将浪费大量的时间和资源

线程池提供了一个解决外部大量用户与服务器有限资源的矛盾,线程池和传统的一个用户对应一 个线程的处理方法不同,它的基本思想就是在程序开始时就提前在内存中开辟一些线程,线程的数目是固定的,他们独自形成一个类,屏蔽了对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时,不是新创建一个线程为其服务,而是从“池子”中选择一个空闲的线程为新的客户请求服务,服务完毕后,线程进入空闲线程池中。如果没有线程空闲的话, 就将数据包暂时积累 , 等待线程池内有线程空闲以后再进行处理。通过对多个任务重用已经存在的线程对象 , 降低了对线程对象创建和销毁的开销。当客户请求时 , 线程对象已经存在 , 可以提高请求的响应时间 , 从而整体地提高了系统服务的表现。

另一种线程池描述

大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是"即时创建,即时销毁"的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁,那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常非常短的情况。

线程池就是为了解决上述问题的,它的实现原理是这样的:在应用程序启动之后,就马上创建一定数量的线程,放入空闲的队列中。这些线程都是处于阻塞状态,这些线程只占一点内存,不占用CPU。当任务到来后,线程池将选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定的数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。

一个线程池主要包括以下几个组成部分

1)线程管理器:用于创建并管理线程池。
2)工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在池中,这些初始化的线程一般处于空闲状态,一般不占用CPU,占用较小的内存空间。
3)任务接口:每个任务必须实现的接口,当线程池的任务队列中有可执行任务时,被空闲的工作线程调去执行(线程的闲与忙是通过互斥量实现的,跟前面文章中的设置标志位差不多),把任务抽象出来形成接口,可以做到线程池与具体的任务无关。
4)任务队列:用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,常用的是队列,主要运用先进先出原理,另外一种是链表之类的数据结构,可以动态的为它分配内存空间,应用中比较灵活,下文中就是用到的链表。

在Linux系统下用C语言创建的一个线程池的步骤:

1) 线程池会维护一个任务链表(每个CThread_worker结构就是一个任务)。
2) pool_init()函数预先创建好max_thread_num个线程,每个线程执thread_routine ()函数。该函数中

while (pool->cur_queue_size == 0)   
{  
    pthread_cond_wait (&(pool->queue_ready),&(pool->queue_lock));  
}  

表示如果任务链表中没有任务,则该线程出于阻塞等待状态。否则从队列中取出任务并执行。
3) pool_add_worker()函数向线程池的任务链表中加入一个任务,加入后通过调用pthread_cond_signal (&(pool->queue_ready))唤醒一个出于阻塞状态的线程(如果有的话)。
4) pool_destroy ()函数用于销毁线程池,线程池任务链表中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出。

应用实例

#include 
#include 
#include 
#include 
#include 
#include 

/*
 * 线程池里所有运行和等待的任务都是一个CThread_worker;
 * 由于所有任务都在链表里,所以是一个链表结构
 */
typedef struct worker
{
    void *(*process)(void *arg); /* 回调函数,任务运行时会调用此函数;也可声明成其它形式 */
    void *arg;                   /* 回调函数的参数 */
    struct worker *next;
} CThread_worker;

/*线程池结构*/
typedef struct
{
    pthread_mutex_t queue_lock;
    pthread_cond_t  queue_ready;
    CThread_worker *queue_head; /*链表结构,线程池中所有等待任务*/
    int shutdown;               /*是否销毁线程池*/
    pthread_t *threadid;
    int max_thread_num;         /*线程池中允许的活动线程数目*/
    int cur_queue_size;         /*当前等待队列的任务数目*/
} CThread_pool;

int pool_add_worker(void *(*process)(void *arg), void *arg);
void *thread_routine(void *arg);
static CThread_pool *g_pool = NULL;

/* 创建活动线程,专门去干活的 */
void pool_init(int max_thread_num)
{
    g_pool = (CThread_pool *)malloc(sizeof(CThread_pool));
    pthread_mutex_init(&(g_pool->queue_lock), NULL);
    pthread_cond_init(&(g_pool->queue_ready), NULL);

    g_pool->queue_head = NULL;
    g_pool->max_thread_num = max_thread_num;
    g_pool->cur_queue_size = 0;
    g_pool->shutdown = 0;
    g_pool->threadid = (pthread_t *)malloc(max_thread_num * sizeof(pthread_t));

    int i = 0;
    for (i = 0; i < max_thread_num; i++)
    {
        pthread_create(&(g_pool->threadid[i]), NULL, thread_routine, NULL);
    }
}

/* 向线程池中加入任务 */
int pool_add_worker(void *(*process)(void *arg), void *arg)
{
    /* 构造一个新任务, 并初始化 */
    CThread_worker *newworker = (CThread_worker *)malloc(sizeof(CThread_worker));
    newworker->process = process;
    newworker->arg = arg;
    newworker->next = NULL;

    pthread_mutex_lock(&(g_pool->queue_lock));
    /* 将任务加入到等待队列中 */
    CThread_worker *tmpHead = g_pool->queue_head;
    if (tmpHead != NULL)
    {
        while (tmpHead->next != NULL)
        {
            tmpHead = tmpHead->next;
        }
        tmpHead->next = newworker;
    }
    else
    {
        g_pool->queue_head = newworker;
    }
    assert(g_pool->queue_head != NULL);
    g_pool->cur_queue_size++;
    pthread_mutex_unlock(&(g_pool->queue_lock));

    printf("*** pthread 0x%x signal, arg: (%d) addr %p ***\n", pthread_self(), *(int *)arg, (int *)arg);
    /* 等待队列中有任务了,唤醒一个等待线程;
       如果所有线程都在忙碌,这句没有任何作用 */
    pthread_cond_signal(&(g_pool->queue_ready));//发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行
    return 0;
}

/* 销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出 */
int pool_destroy(void)
{
    if (g_pool->shutdown)
    {
        return -1; /*防止两次调用*/
    }
    g_pool->shutdown = 1;

    /*唤醒所有等待线程,线程池要销毁了*/
    pthread_cond_broadcast(&(g_pool->queue_ready));

    /*阻塞等待线程退出,否则就成僵尸了*/
    int i;
    for (i = 0; i < g_pool->max_thread_num; i++)
    {
        pthread_join(g_pool->threadid[i], NULL);
    }
    free(g_pool->threadid);

    /*销毁等待队列*/
    CThread_worker *head = NULL;
    while (g_pool->queue_head != NULL)
    {
        head = g_pool->queue_head;
        g_pool->queue_head = g_pool->queue_head->next;
        free(head);
    }
    /*条件变量和互斥量也别忘了销毁*/
    pthread_mutex_destroy(&(g_pool->queue_lock));
    pthread_cond_destroy(&(g_pool->queue_ready));

    free(g_pool);
    /*销毁后指针置空是个好习惯*/
    g_pool = NULL;
    return 0;
}

void *thread_routine(void *arg)
{
    printf("[starting thread] 0x%x\n", pthread_self());
    while (1)
    {
        pthread_mutex_lock(&(g_pool->queue_lock));
        /*如果等待队列为0并且不销毁线程池,则处于阻塞状态;
         * 注意: pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
        while (g_pool->cur_queue_size == 0 && !g_pool->shutdown)
        {
            printf("thread      0x%x is waiting\n", pthread_self());
            pthread_cond_wait(&(g_pool->queue_ready), &(g_pool->queue_lock));
        }

        if (g_pool->shutdown)
        {
            pthread_mutex_unlock(&(g_pool->queue_lock));
            printf("thread      0x%x will exit\n", pthread_self());
            pthread_exit(NULL);
        }
        printf("thread      0x%x is starting to work   [我要开始干活了]\n", pthread_self());

        /*assert是调试的好帮手*/
        assert(g_pool->cur_queue_size != 0);
        assert(g_pool->queue_head != NULL);

        g_pool->cur_queue_size--;
        CThread_worker *worker = g_pool->queue_head;
        g_pool->queue_head = worker->next;
        pthread_mutex_unlock(&(g_pool->queue_lock));

        /* 调用回调函数,执行任务 */
        (*(worker->process))(worker->arg);
        free(worker);
        worker = NULL;
    }
    printf("pthread_exit (NULL)?????\n");
    /*这一句应该是不可达的*/
    pthread_exit(NULL);
}

void *myprocess(void *arg)
{
    printf("threadid is 0x%x, working on task {%d}  [一秒钟活干完了]\n", pthread_self(), *(int *)arg);
    sleep(1); /*休息一秒,延长任务的执行时间*/
    return NULL;
}


int main(int argc, char **argv)
{
    pool_init(3); /*线程池中最多3个活动线程*/
                                                    // 3个人去做10件事情
    sleep(1);
                  /*连续向池中投入10个任务*/     
    int *workingnum = (int *)malloc(sizeof(int) * 10);
    int i;
    for (i = 0; i < 10; i++)
    {
        workingnum[i] = i;
        pool_add_worker(myprocess, &workingnum[i]);
    }
    /*等待所有任务完成*/
    sleep(5);

    pool_destroy();
    free(workingnum);
    return 0;
}

含有线程的应用程序编译时,需要加 -pthread,表示引用gcc编译使用了POSIX thread的库函数

(pthread是动态库,需要用-lpthread,所有的动态库都需要用-lxxx来引用gcc编译使用了POSIX thread的程序时通常需要加额外的选项)

root@cloudserver:~/robot/code/dock_ble_ota/M146470-zgltest# ./a.out
[starting thread] 0x4a204700
thread      	  0x4a204700 is waiting
[starting thread] 0x4aa05700					// 分配这3个人去做10件事
thread      	  0x4aa05700 is waiting
[starting thread] 0x4b206700
thread      	  0x4b206700 is waiting

*** pthread 0x4ba2f740 signal, arg: (0) addr 0x55d0fd556660 ***
*** pthread 0x4ba2f740 signal, arg: (1) addr 0x55d0fd556664 ***
thread      0x4a204700 is starting to work   [我要开始干活了]
threadid is 0x4a204700, working on task {0}  [一秒钟活干完了]
*** pthread 0x4ba2f740 signal, arg: (2) addr 0x55d0fd556668 ***
thread      0x4aa05700 is starting to work   [我要开始干活了]
threadid is 0x4aa05700, working on task {1}  [一秒钟活干完了]
*** pthread 0x4ba2f740 signal, arg: (3) addr 0x55d0fd55666c ***
thread      0x4b206700 is starting to work   [我要开始干活了]
threadid is 0x4b206700, working on task {2}  [一秒钟活干完了]
*** pthread 0x4ba2f740 signal, arg: (4) addr 0x55d0fd556670 ***
*** pthread 0x4ba2f740 signal, arg: (5) addr 0x55d0fd556674 ***
*** pthread 0x4ba2f740 signal, arg: (6) addr 0x55d0fd556678 ***
*** pthread 0x4ba2f740 signal, arg: (7) addr 0x55d0fd55667c ***
*** pthread 0x4ba2f740 signal, arg: (8) addr 0x55d0fd556680 ***
*** pthread 0x4ba2f740 signal, arg: (9) addr 0x55d0fd556684 ***
thread      0x4a204700 is starting to work   [我要开始干活了]
threadid is 0x4a204700, working on task {3}  [一秒钟活干完了]
thread      0x4aa05700 is starting to work   [我要开始干活了]
threadid is 0x4aa05700, working on task {4}  [一秒钟活干完了]
thread      0x4b206700 is starting to work   [我要开始干活了]
threadid is 0x4b206700, working on task {5}  [一秒钟活干完了]
thread      0x4a204700 is starting to work   [我要开始干活了]
threadid is 0x4a204700, working on task {6}  [一秒钟活干完了]
thread      0x4aa05700 is starting to work   [我要开始干活了]
threadid is 0x4aa05700, working on task {7}  [一秒钟活干完了]
thread      0x4b206700 is starting to work   [我要开始干活了]
threadid is 0x4b206700, working on task {8}  [一秒钟活干完了]
thread      0x4a204700 is starting to work   [我要开始干活了]
threadid is 0x4a204700, working on task {9}  [一秒钟活干完了]

thread      0x4aa05700 is waiting
thread      0x4b206700 is waiting
thread      0x4a204700 is waiting
thread      0x4aa05700 will exit
thread      0x4b206700 will exit
thread      0x4a204700 will exit

其他博文

https://subingwen.cn/linux/threadpool/

你可能感兴趣的:(Linux系统编程,linux,c语言)