简单线程池C语言实现

一个简单线程池的实现

一、什么是线程池?

线程池的官方定义:

线程池是一种成熟的线程使用模式。实现由领导者与跟随者模式,半同步半异步模式。线程池的伸缩性对性能有较大的影响。创建太多线程,将会浪费一定的资源,有些线程池未被充分使用。销毁太多线程,将导致之后浪费时间再次创建它们。创建线程太慢,将会导致长时间的等待,性能变差。销毁线程池太慢,导致其他线程资源饥饿。


二、为什么用线程池?

1. 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率,而线程池能有效的减少线程的创建和销毁次数。
2. 如果线程的并发数量过多,线程之间抢占所属进程资源的情况就很严重,很容易导致阻塞,而使用线程池能有效的控制线程的最大并发数,有效避免阻塞问题。

三、什么时候用线程池?

1. 单个任务处理的时间比较短
2. 需要处理的任务数量大

四、什么时候不适宜用线程池?

1. 线程执行需要很长时间,也就是单个的任务非常耗时
2. 需要为线程指定详细的优先级,我们用的线程池都是基于任务队列来排队,先来先执行
3. 在执行过程中需要对线程进行操作,比如睡眠,挂起等,因为线程池本身就是为突然大量爆发的短任务而设计的,如果要使线程睡眠、挂起的话,是与其设计思想相悖的!

五、说了这么多,心动不如行动,我们使用C语言在Linux平台下实现一个简单的线程池

1.首先我们要控制线程池,去为我们执行任务,我们就必须知道一些信息:
(1).当前总共有多少个线程?
(2).当前有多少个空闲的线程可供使用?
(3).线程总数的上限是多少?
(4).我们的任务是通过一个任务队列来管理的,那么控制线程池的执行,也需要直到当前任务队列的头和尾在哪,我需要通过它来知道当前有没有任务,和从队列中取任务
(5).我们要知道什么时候要销毁线程池,所以也要设置一个销毁标记
(6).因为是在多线程下工作,所以需要使用互斥量和条件变量来协调各个线程和保证线程安全
(7).因为要创建任务队列,所以也需要定义任务结点结构
2.知道了上面这些要求,那么我们设计出来的线程池控制结构应该是这样的:
//任务结点结构
typedef struct task
{
    void *(*run)(void *arg); //任务回调函数
    void *arg;               //任务回调函数的参数
    struct task *next;       //下一个任务结点
}task_t;  

//封装互斥量和条件变量为一个condition_t
typedef struct 
{
    pthread_mutex_t  mutex;    //互斥量
    pthread_cond_t   cond;     //条件变量
}condition_t;  

//线程池结构
typedef struct 
{
    condition_t ready;       //同步、互斥条件
    task_t      *first;      //任务队列头结点指针
    task_t      *last;       //任务队列尾结点指针
    int         counter;     //当前线程总数
    int         idle;        //当前空闲线程数
    int         quit;        //销毁线程池标志
    int         max_threads; //线程池允许最大线程数
}threadpool_t;    
3.对互斥量和条件变量的操作,我们为了方便也要进行一次封装:
condition.h文件:
#ifndef _CONDITION_H
#include <pthread.h>

//封装互斥量和条件变量为一个condition_t
typedef struct 
{
    pthread_mutex_t  mutex;    //互斥量
    pthread_cond_t   cond;     //条件变量
}condition_t;

//同步、互斥量的初始化
void condition_init(condition_t *cond);

//互斥量加锁
void condition_lock(condition_t *cond);

//互斥量解锁
void condition_unlock(condition_t *cond);

//条件变量阻塞等待
int  condition_wait(condition_t *cond);

//条件变量指定超时时间等待
int  condition_timewait(condition_t *cond, const struct timespec *abstime);

//给一个等待中的条件变量发送唤醒信号
void condition_signal(condition_t *cond);

//广播发送给所有等待中的条件变量唤醒信号
void condition_broadcast(condition_t *cond);

//销毁互斥量和条件变量
void condition_destroy(condition_t *cond);
#define _CONDITION_H
#endif  
condition.c文件:
#include "condition.h"

//同步、互斥量的初始化
void condition_init(condition_t *cond)
{
    pthread_mutex_init(&cond->mutex, NULL);
    pthread_cond_init(&cond->cond, NULL);
}

//互斥量加锁
void condition_lock(condition_t *cond)
{
    pthread_mutex_lock(&cond->mutex);
}

//互斥量解锁
void condition_unlock(condition_t *cond)
{
    pthread_mutex_unlock(&cond->mutex);
}

//条件变量阻塞等待
int  condition_wait(condition_t *cond)
{
    return pthread_cond_wait(&cond->cond, &cond->mutex);
}

//条件变量指定超时时间等待
int  condition_timedwait(condition_t *cond, const struct timespec *abstime)
{
    return pthread_cond_timedwait(&cond->cond, &cond->mutex, abstime);
}

//给一个等待中的条件变量发送唤醒信号
void condition_signal(condition_t *cond)
{
    pthread_cond_signal(&cond->cond);
}

//广播发送给所有等待中的条件变量唤醒信号
void condition_broadcast(condition_t *cond)
{
    pthread_cond_broadcast(&cond->cond);
}

//销毁互斥量和条件变量
void condition_destroy(condition_t *cond)
{
    pthread_mutex_destroy(&cond->mutex);
    pthread_cond_destroy(&cond->cond);
}  
4.操作线程池的函数进行封装,以便其他人调用
threadpool.h文件:
#ifndef _THREADPOOL_H
#include "condition.h"

//任务结点结构
typedef struct task
{
    void *(*run)(void *arg); //任务回调函数
    void *arg;               //任务回调函数的参数
    struct task *next;       //下一个任务结点
}task_t;

//线程池结构
typedef struct 
{
    condition_t ready;       //同步、互斥条件
    task_t      *first;      //任务队列头结点指针
    task_t      *last;       //任务队列尾结点指针
    int         counter;     //当前线程总数
    int         idle;        //当前空闲线程数
    int         quit;        //销毁线程池标志
    int         max_threads; //线程池允许最大线程数
}threadpool_t;


//初始化线程池
//@threadpool  : 线程池结构指针
//@max_threads : 指定的线程池中最大线程数
void threadpool_init(threadpool_t *threadpool, int max_threads);


//向线程池的任务队列中添加任务
//@threadpool  : 线程池结构指针
//@run         : 任务回调函数
//@arg         : 任务回调函数的参数
void threadpool_add_task(threadpool_t *threadpool, void *(*run)(void *), void *arg);


//销毁线程池
//@threadpool  : 线程池结构指针
void threadpool_destroy(threadpool_t *threadpool);



#define _THREADPOOL_H
#endif
threadpool.c文件:
#include <malloc.h>
#include <errno.h>
#include "threadpool.h"


//初始化线程池
//@threadpool  : 线程池结构指针
//@max_threads : 指定的线程池中最大线程数
void threadpool_init(threadpool_t *threadpool, int max_threads)
{
    //初始化同步、条件变量
    condition_init(&(threadpool->ready));

    //设置任务队列头、尾指针
    threadpool->first = NULL;
    threadpool->last = NULL;

    //初始化当前线程总数和空闲线程数为0
    threadpool->counter = 0;
    threadpool->idle = 0;

    //设置线程池最大线程数
    threadpool->max_threads = max_threads;

    //销毁线程池标志设置为0(不销毁), 为1则销毁
    threadpool->quit = 0;
}

//线程处理函数
void *route(void *arg)
{
    //分离线程
    pthread_detach(pthread_self());

    //获取线程池结构指针
    threadpool_t *threadpool = (threadpool_t *)arg;

    //超时标志
    int timeout = 0;

    while(1)
    {
        //上锁
        condition_lock(&threadpool->ready);

        //超时标志重置
        timeout = 0;  

        //当线程创建好,但任务尚未执行时,或者上次任务执行完毕之后再次进入循环,该线程属于空闲线程,所以空闲线程数+1
        threadpool->idle++;

        //当任务队列为空并且还没有开始销毁线程时,线程进入等待状态
        while(threadpool->first == NULL && threadpool->quit == 0)
        {
            //获取系统当前时间
            struct timespec wait_time;
            clock_gettime(CLOCK_REALTIME, &wait_time);

            //超时等待时间为3秒钟, 如果3秒内没有被唤醒(当新任务来时,会发送唤醒信号),则返回。
            wait_time.tv_sec += 3;

            int ret = condition_timedwait(&threadpool->ready, &wait_time);
            if(ret == ETIMEDOUT)
            {
                printf("%#x thread timeout!\n", (int)pthread_self());

                //设置超时标志
                timeout = 1;

                break;
            }
        }

        //程序能走到这里,有三种情况: 1.等待超时;  2.任务队列不为空 3.被销毁线程池时发送的广播信号唤醒
        //而无论哪种情况,空闲线程都应该 - 1
        threadpool->idle--;

        //第一种情况: 等待超时而且当前无任务可执行,则销毁该线程
        if(timeout == 1 && threadpool->first == NULL)
        {
            threadpool->counter--;
            condition_unlock(&threadpool->ready);
            break;
        }

        //第二种情况: 任务队列不为空, 则执行任务
        if(threadpool->first != NULL)
        {
            //取出队头任务
            task_t *task = threadpool->first;
            threadpool->first = threadpool->first->next;

            //为了避免任务执行时间过长,别的线程获取不到锁,所以先解锁后加锁,绕过任务执行函数
            condition_unlock(&threadpool->ready);  //解锁
            task->run(task->arg);                  //执行任务
            condition_lock(&threadpool->ready);    //加锁

            //任务执行完毕释放任务结点
            free(task);
        }
        //第三种情况: 下达销毁线程池命令, 且当前无任务可执行,则销毁线程
        if(threadpool->quit == 1 && threadpool->first == NULL)
        {
            threadpool->counter--;

            //说明是线程池最后一个线程, 应该发送一个唤醒信号让销毁线程池的函数继续往下执行
            if(threadpool->counter == 0)
            {
                condition_signal(&threadpool->ready);
            }

            //解锁
            condition_unlock(&threadpool->ready);
            break;
        }

        //这里的解锁主要是为任务执行完毕后的解锁
        condition_unlock(&threadpool->ready);
    }        

}


//向线程池的任务队列中添加任务
//@threadpool  : 线程池结构指针
//@run         : 任务回调函数
//@arg         : 任务回调函数的参数
void threadpool_add_task(threadpool_t *threadpool, void *(*run)(void *), void *arg)
{
    //上锁(对队列进行增删查改需要互斥访问)
    condition_lock(&threadpool->ready);

    //构造新任务结点
    task_t *new_task = malloc(sizeof(task_t));   
    new_task->run = run;
    new_task->arg = arg;
    new_task->next = NULL;


    //如果任务队列为空
    if(threadpool->first == NULL)
    {
        threadpool->first = new_task;
    }
    else
    {//如果任务队列不为空
        threadpool->last->next = new_task;
    }

    //修正任务队列尾指针
    threadpool->last = new_task;

    //如果有空闲线程,则说明可能有空闲线程在等待执行任务
    if(threadpool->idle > 0)
    {
        //唤醒一个在等待状态的空闲线程
        condition_signal(&threadpool->ready);
    }

    //没有空闲线程, 而且当前总线程数小于线程池最大线程个数,则要创建一个新线程
    else if(threadpool->counter < threadpool->max_threads)
    {
        pthread_t tid;

        //创建出新线程并将线程池结构指针作为线程处理函数的参数
        pthread_create(&tid, NULL, route, threadpool);

        //当前线程池总线程数数加1
        threadpool->counter++;
    }

    //解锁
    condition_unlock(&threadpool->ready);
}


//销毁线程池
//@threadpool  : 线程池结构指针
void threadpool_destroy(threadpool_t *threadpool)
{
    //说明有线程已经在处理销毁线程池了,其他线程就不能进去了
    if(threadpool->quit == 1)
    {
        return;
    }

    //上锁
    condition_lock(&threadpool->ready);
    threadpool->quit = 1;   //标志着已经开始销毁线程池了

    //如果有空闲线程, 则说明有线程处于等待状态,则要将它们全部唤醒,让它们自行销毁
    if(threadpool->idle > 0)
    {
        condition_broadcast(&threadpool->ready);
    }
    else
    {//没有空闲线程,则等待所有正在执行任务的线程执行完自己的任务后自行销毁
        while(threadpool->counter > 0)
        {
            //阻塞等待所有正在执行任务的线程执行完毕,当最后一个线程执行完毕时,
            //会给发送一个唤醒信号给这里,让销毁线程也退出
            condition_wait(&threadpool->ready);
        }
    }

    //销毁互斥、条件变量
    condition_destroy(&threadpool->ready);

    //解锁
    condition_unlock(&threadpool->ready);
}   

好,我们这就写好了一个线程池,我们写一个测试程序来跑一下,看是否正确:

main.c文件:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "threadpool.h"

void *mytask(void *arg)
{
    printf("thread %#x working on %d\n", pthread_self(), (int)arg);
    sleep(1);
}

int main(void)
{
    threadpool_t pool;

    //初始化线程池,规定其线程最大数为3
    threadpool_init(&pool, 3);

    int i;
    for(i = 0; i < 10; i++)
    {
        threadpool_add_task(&pool, mytask, (void *)i);
    }

    sleep(10);
    printf("总线程数为%d\n", pool.counter);
    printf("空闲线程数为%d\n", pool.idle);
    threadpool_destroy(&pool);
    return 0;
}  

五、运行截图:

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言实现一个简单线程池可以按照以下步骤进行: 1. 定义线程池结构体:创建一个结构体,包含线程池中的线程数量、任务队列、互斥锁、条件变量等必要的成员。 ```c typedef struct { pthread_t *threads; // 线程数组 int thread_count; // 线程数量 task_queue_t *task_queue; // 任务队列 pthread_mutex_t mutex; // 互斥锁 pthread_cond_t cond; // 条件变量 int shutdown; // 线程池关闭标志 } thread_pool_t; ``` 2. 定义任务队列结构体:创建一个结构体,用于存储任务队列的信息,包括任务数组、任务数量、头尾指针等成员。 ```c typedef struct { task_t **tasks; // 任务数组 int task_count; // 任务数量 int head; // 队列头指针 int tail; // 队列尾指针 } task_queue_t; ``` 3. 定义任务结构体:创建一个结构体,用于存储具体的任务信息,例如任务函数指针和参数。 ```c typedef struct { void (*function)(void *); // 任务函数指针 void *argument; // 任务参数 } task_t; ``` 4. 初始化线程池:创建线程池,并初始化线程数组、任务队列、互斥锁、条件变量等。 5. 添加任务:将任务添加到任务队列中,并通过条件变量通知线程池中的线程有新任务可执行。 6. 线程函数:线程池中的线程函数,循环从任务队列中取出任务并执行。 7. 等待线程池任务完成:可以通过条件变量等待所有任务执行完毕。 8. 销毁线程池:释放线程池中的资源,包括线程数组、任务队列、互斥锁、条件变量等。 这是一个简单线程池实现的框架,你可以根据自己的需求进行具体的功能扩展和优化。注意在并发环境中使用互斥锁和条件变量来保证线程安全。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值