池式组件(1)线程池

一、池式组件

        池式组件是一种常用的软件设计模式,旨在提高资源利用率和系统性能。通过维护一个资源对象的集合(即“池”),并根据需要向请求者分配和回收这些资源,从而避免了频繁创建和销毁资源所带来的开销。这些资源对象可以是线程、内存、数据库连接等。因此分为线程池、内存池、数据库连接池。

二、线程池

        线程池是一种多线程处理形式,用于有效利用服务器上的线程资源。它允许线程多次复用,且每次复用的线程可以处理不同的任务,从而节省创建与销毁线程的开销。

线程池的作用

  1. 降低资源消耗:线程创建和销毁是一个耗时的过程,通过线程池可以避免频繁的线程创建和销毁,从而降低资源消耗。
  2. 提高相应速度:当有新任务到达时,如果线程池中有空闲的线程,任务可以立即被执行。
  3. 提高线程的客观理性:通过线程池可以方便的控制同时运行的线程数量。当线程过多时会导致资源竞争和性能下降。
  4. 异步执行耗时任务:多个耗时任务可以由多个消费者线程分别执行操作。

线程数量选择

耗时任务可以分为两大类:IO密集型和CPU密集型。

IO密集型:该类任务主要受到输入/输出操作的限制。CPU在这些任务中大部分时间都在等待IO操作的完成。如网络数据传输、文件读写操作、数据库查询。

CPU密集型:该类任务主要受到CPU计算能力的限制。需要大量的CPU时间来执行复杂的计算或算法。如复杂的数学计算,图像和视频处理,大规模数据排序和搜索。

(io等待时间+cpu运算时间)*核心数/cpu运算时间。根据公式,一般CPU密集型的线程数量为CPU内核数;IO密集型的线程池线程数量等于2倍核心数+2;

构成

        生产者线程:发布任务,将任务添加到任务队列里。

        任务队列:用于存放任务,任务先进先出。任务的内容包括任务的上下文以及任务的执行函数。

        消费者线程(线程池):是固定数量线程的集合,负责从任务队列中取出任务,并执行任务。

三、代码实现

结构体

//任务
typedef struct task_s {
    void *next;
    handler_pt func;
    void *arg;
} task_t;

//任务队列
typedef struct task_queue_s {
    void *head;
    void **tail; 
    int block;
    spinlock_t lock;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} task_queue_t;

//线程池
struct thrdpool_s {
    task_queue_t *task_queue;
    atomic_int quit;
    int thrd_count;
    pthread_t *threads;
};

        结构体任务有三个参数:next指针指向下一个任务,func是任务的执行函数,以及arg任务的上下文执行函数的参数。

        任务队列有六个参数:head指向头节点任务,二级指针tail指向末尾任务,block是队列的阻塞状态,lock是自旋锁,mutex是互斥锁,cond是条件变量。tail用二级指针为了添加任务时更好操作,后文会详细介绍。

        条件变量用来进行线程的调度:线程有两种状态即从没有任务到有任务,从有任务到没有任务。利用条件变量,当队列有任务时唤醒线程;当没有任务时将线程休眠。

        线程池有四个参数:任务队列,线程池状态(运行或终止),线程数量,线程;

接口设计

 

创建线程池

创建任务队列
static task_queue_t *
__taskqueue_create() {
    int ret;
//为任务队列分配内存
    task_queue_t *queue = (task_queue_t *)malloc(sizeof(task_queue_t));
//如果分配成功
    if (queue) {
//初始化互斥锁
        ret = pthread_mutex_init(&queue->mutex, NULL);
//初始化成功
        if (ret == 0) {
//初始化条件变量
            ret = pthread_cond_init(&queue->cond, NULL);
//如果初始化成功
            if (ret == 0) {
//初始化自旋锁
                spinlock_init(&queue->lock);
                queue->head = NULL;
                queue->tail = &queue->head;
                queue->block = 1;//队列为空设置为阻塞
                return queue;
            }
//如果创建失败销毁
            pthread_mutex_destroy(&queue->mutex);
        }
        free(queue);
    }
    return NULL;
}
添加任务
static inline void 
__add_task(task_queue_t *queue, void *task) {
// 将task转换为二级指针,task的起始内存位置为指向下一个链表节点的指针
    void **link = (void**)task;
//初始化下一个任务为空
    *link = NULL;
//加锁以保护队列的并发
    spinlock_lock(&queue->lock);
//tail为二级指针,因此*queue->tail 就是queue->tail->next,将原来最后一个任务的next指针指向新任务
    *queue->tail /* 等价于 queue->tail->next */ = link;
//队列末尾指针指向新任务
    queue->tail = link;
//解锁
    spinlock_unlock(&queue->lock);
//发送条件变量信号,通知可能在等待新任务到来的线程
    pthread_cond_signal(&queue->cond);//唤醒在cond上休眠的线程
}

 在这段代码中,体现了为什么tail要用二级指针。

利用条件变量,当有任务时,唤醒在cond上休眠的线程。

弹出头部任务
static inline void * 
__pop_task(task_queue_t *queue) {
//加锁
    spinlock_lock(&queue->lock);
//如果队列为空解锁并返回错误值
    if (queue->head == NULL) {
        spinlock_unlock(&queue->lock);
        return NULL;
    }
//有任务时,获取头部任务的地址
    task_t *task;
    task = queue->head;
//link指向第二个任务。*link为第二个任务的地址
    void **link = (void**)task;
    queue->head = *link;
//如果第二个任务为空,设置尾指针指向空
    if (queue->head == NULL) {
        queue->tail = &queue->head;
    }
//解锁
    spinlock_unlock(&queue->lock);
//返回头部任务
    return task;
}
 获取头部任务(给消费者线程使用)
static inline void * 
__get_task(task_queue_t *queue) {
    task_t *task;
    // 虚假唤醒,检查是否有事件。可能是系统内的信号唤醒的线程
    //如果弹出任务
    while ((task = __pop_task(queue)) == NULL) {
        //加锁
        pthread_mutex_lock(&queue->mutex);
        if (queue->block == 0) {//判断是不是阻塞的,如果是非阻塞的返回出去
            pthread_mutex_unlock(&queue->mutex);
            return NULL;
        }//关闭线程池的时候会把队列设置为非阻塞的
		//如果是阻塞的,消费者线程等待取任务
		// 1. 先 unlock(&mtx)
		// 2. 在 cond 休眠
        // --- __add_task 时唤醒
        // 3. 在 cond 唤醒
        // 4. 加上 lock(&mtx);
        pthread_cond_wait(&queue->cond, &queue->mutex);
        //解锁
        pthread_mutex_unlock(&queue->mutex);
    }
    return task;
}
线程函数
static void *
__thrdpool_worker(void *arg) {
    thrdpool_t *pool = (thrdpool_t*) arg;
    task_t *task;
    void *ctx;
//当线程池运行时
    while (atomic_load(&pool->quit) == 0) {
//获取队首的任务
        task = (task_t*)__get_task(pool->task_queue);
        if (!task) break;
//获取任务函数
        handler_pt func = task->func;
//获取参数
        ctx = task->arg;
//销毁任务
        free(task);
//执行任务
        func(ctx);
    }
    
    return NULL;
}
 
创建线程
static int 
__threads_create(thrdpool_t *pool, size_t thrd_count) {
    pthread_attr_t attr;//用于描述线程的属性
	int ret;
//初始化attr,将attr设置为默认的线程属性
    ret = pthread_attr_init(&attr);
//如果初始化成功
    if (ret == 0) {
//为所有线程创建内存
        pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thrd_count);
//如果创建成功
        if (pool->threads) {
            int i = 0;
            for (; i < thrd_count; i++) {
//创建每个线程
                if (pthread_create(&pool->threads[i], &attr, __thrdpool_worker, pool) != 0) {
                    break;
                }
            }
            pool->thrd_count = i;
            pthread_attr_destroy(&attr);
//如果创建成功
            if (i == thrd_count)
                return 0;
//创建失败
            __threads_terminate(pool);
            free(pool->threads);
        }
        ret = -1;
    }
    return ret; 
}
创建并初始化线程池(接口)
thrdpool_t *
thrdpool_create(int thrd_count) {
    thrdpool_t *pool;
    pool = (thrdpool_t*)malloc(sizeof(*pool));

    if (pool) {
//创建任务队列
        task_queue_t *queue = __taskqueue_create();
        if (queue) {
            pool->task_queue = queue;
//设置线程池运行
            atomic_init(&pool->quit, 0);
//创建线程
            if (__threads_create(pool, thrd_count) == 0)
                return pool;
//如果创建失败则销毁
            __taskqueue_destroy(queue);
        }
        free(pool);
    }
    return NULL;
}
 

提交新任务(接口)

int
thrdpool_post(thrdpool_t *pool, handler_pt func, void *arg) {
//如果线程池不在运行,返回错误值
    if (atomic_load(&pool->quit) == 1) 
        return -1;
//为新任务分配内存
    task_t *task = (task_t*) malloc(sizeof(task_t));
    if (!task) return -1;
    task->func = func;
    task->arg = arg;
//将新任务添加到任务队列
    __add_task(pool->task_queue, task);
    return 0;
}

终止线程池

将任务队列设置为非阻塞,并唤醒所有线程
static void
__nonblock(task_queue_t *queue) {
    pthread_mutex_lock(&queue->mutex);
    queue->block = 0;//将任务队列设置为非阻塞
    pthread_mutex_unlock(&queue->mutex);
    pthread_cond_broadcast(&queue->cond);//广播条件变量来唤醒所有等待的线程
}
终止(接口)
void
thrdpool_terminate(thrdpool_t * pool) {
    atomic_store(&pool->quit, 1);
    __nonblock(pool->task_queue);
}

销毁线程池

销毁
static void
__taskqueue_destroy(task_queue_t *queue) {
    task_t *task;
//弹出任务队列所有任务
    while ((task = __pop_task(queue))) {
        free(task);
    }
//销毁自旋锁,互斥锁,条件变量,任务队列
    spinlock_destroy(&queue->lock);
    pthread_cond_destroy(&queue->cond);
    pthread_mutex_destroy(&queue->mutex);
    free(queue);
}
等待线程完成销毁线程池(接口)
void
thrdpool_waitdone(thrdpool_t *pool) {
    int i;
    for (i=0; i<pool->thrd_count; i++) {
//阻塞调用join的线程,直到线程终止。是同步机制,确保线程间的正确执行顺序
		pthread_join(pool->threads[i], NULL);
    }
//销毁任务队列
    __taskqueue_destroy(pool->task_queue);
//销毁线程,线程池
    free(pool->threads);
    free(pool);
}

https://github.com/0voice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值