Linux中线程池的原理与实现案例详解

通俗版解释:什么是“池”?

想象一下,你去图书馆借书。如果每次借书都要先找管理员临时打印一本新书,效率是不是很低?但如果图书馆提前把所有书都准备好放在书架上,你只需要“租借”已有的书,就能省下很多时间。这就是“池”的原理!

1. 什么是“池”?

池 = 资源仓库
服务器在启动时,会提前准备好一堆资源(比如内存、线程),就像图书馆提前把所有书摆好。这些资源被集中管理,随时待命。
为什么这么做?
因为如果每次需要资源时都临时申请(比如动态分配内存、创建线程),就像每次借书都要临时打印新书,既慢又浪费时间。而“池”就像一个“资源仓库”,直接从里面拿资源,快多了!

2. 常见的“池”有哪些?

(1)内存池

问题:
通常我们用 new 或 malloc 申请内存,但每次申请的大小不一,就像拼图时碎片化地买积木,最后可能会有很多“边角料”没用上(内存碎片),效率低下。
解决方案:
提前申请一大块内存,切成大小相同的“积木块”备用。需要时直接拿一块,用完了放回仓库。这样既省时间,又减少碎片。

(2)线程池

问题:
创建和销毁线程就像每次打车都要叫一辆新车,成本高、耗时长。比如处理网页请求时,如果每次都新建线程,服务器会“忙不过来”。
解决方案:
提前准备一堆“出租车”(线程),用的时候直接租一辆,用完再还回去。比如快餐店高峰期,用员工轮班制(线程池)比临时招人快多了!

3. 线程池适合什么场景?

适合:
短任务高频次:比如网页请求(几秒内完成),任务多但单个任务耗时短。
例子:快餐店高峰期,用线程池快速处理订单。
突发流量:比如双十一抢购,突然涌入大量请求,线程池能快速响应。
例子:演唱会门票秒杀,用线程池应对瞬时高并发。
不适合:
长任务:比如 Telnet 连接(几十分钟),线程池的优势不明显。
例子:按摩店需要长时间服务,线程池(员工轮班)反而可能让客人等很久。

4. 用“池”的好处

**省时间:**直接拿资源,不用临时申请。
比如:图书馆书架 vs 临时打印新书。
**省资源:**减少碎片,避免浪费。
比如:内存池的“积木块”比零散的碎片更高效。
稳定高效:资源提前准备好,应对突发流量不慌。
比如:线程池能快速应对双十一抢购。

线程池的实现示例

Linux系统下用C语言创建的一个线程池。简单的来说,在程序初始的时候创建一批线程放入线程池中,同时需要维护一组任务worker,按照某种分配策略让线程竞争任务。当没有任务需要执行的时候,所有线程都会阻塞;当任务数量过多的时候,任务在队列中等待。

其中,任务队列使用链表的方式实现,同时,需要用到 锁 和 条件变量 来实现互斥和共享。具体细节简单如下:

  1. pool_init()函数预先创建好若干个个线程,每个线程执thread_routine ()函数。该函数中
while (pool->cur_queue_size == 0)  {
      pthread_cond_wait (&(pool->queue_ready),&(pool->queue_lock));
}

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

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
 
// 线程池里所有运行和等待的任务都是一个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;
    // 线程id数组
    pthread_t *threadid;
 
    // 是否销毁线程池
    int shutdown;
 
    // 线程池中线程数目
    int max_thread_num;
    // 等待队列的任务数目
    int cur_queue_size;
 
} CThread_pool;
 
/****** 线程池主要功能 ******/
// 初始化线程池
void pool_init(int max_thread_num); 
// 清除任务
void pool_destroy();
// 往任务队列中添加任务
int pool_add_worker(void *(*process)(void *arg), void *arg);
// 线程体执行的操作
void *thread_routine(void *arg);
 
 
// 示例程序
void *myprocess(void *arg);
 
static CThread_pool *pool = NULL;
 
void pool_init(int max_thread_num) {
    pool = (CThread_pool *) malloc (sizeof (CThread_pool));    
    
    pthread_mutex_init(&(pool->queue_lock), NULL);
    pthread_cond_init(&(pool->queue_ready), NULL);
 
    pool->queue_head = NULL;
    pool->max_thread_num = max_thread_num;
    pool->shutdown = 0;
 
    pool->threadid = (pthread_t *) malloc(sizeof(pthread_t) * max_thread_num);
    int i;
    for (i = 0; i < max_thread_num; i++) {
        pthread_create(&(pool->threadid[i]), NULL, thread_routine, NULL);
    }
}
 
// 销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出
void pool_destroy() {
    if (pool->shutdown)
        return;  // 防止多次调用
    pool->shutdown = 1;
    // 通知所有的线程,线程池要销毁
    pthread_cond_broadcast (&(pool->queue_ready));
    // 释放线程池中的threadid
    int i = 0;
    for (i = 0; i < pool->max_thread_num; i++) 
        pthread_join(pool->threadid[i], NULL);
    free(pool->threadid);
    // 释放所有的等待队列
    while (pool->queue_head != NULL) {
        CThread_worker *head= pool->queue_head;
        pool->queue_head = pool->queue_head ->next;
        free(head);
    }
    // 销毁lock和condition
    pthread_mutex_destroy(&(pool->queue_lock));
    pthread_cond_destroy(&(pool->queue_ready));
 
    // 最后free掉pool,销毁后将指针置空
    // 销毁后指针置空是个好习惯
    free(pool);
    pool = NULL;
    return;
} 
 
// 向线程池中加入任务
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(&(pool->queue_lock));
    CThread_worker *member = pool->queue_head;
    if (member != NULL) {
        while (member->next != NULL)
            member = member->next;
        member->next = newworker;
    }
    else 
        pool->queue_head = newworker;
 
    assert(pool->queue_head != NULL);
 
    pool->cur_queue_size++;
    pthread_mutex_unlock(&(pool->queue_lock));
 
    // 等待队列中有任务了,唤醒一个等待线程, 注意如果所有线程都在忙碌,这句没有任何作用
    pthread_cond_signal(&(pool->queue_ready));
    return 0;
}
 
// 线程体执行的操作
void *thread_routine(void *arg) {
    printf("starting thread 0x%x\n", pthread_self());
    while (1) {
        // 因为要操作worker队列,因此先要加锁
        pthread_mutex_lock(&(pool->queue_lock));
        // 如果等待队列为0并且不销毁线程池,则处于阻塞状态;  
        // 注意pthread_cond_wait是一个原子操作,等待时会释放锁,唤醒后会加锁
        while (pool->cur_queue_size == 0 && !pool->shutdown) {
            // 遇到break,continue,return等跳转语句,千万不要忘记先解锁
            printf("thread 0x%x is waiting\n", pthread_self());
            pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));
        }
        // 销毁线程池
        if (pool->shutdown == 1) {
            pthread_mutex_unlock(&(pool->queue_lock));
            printf("thread 0x%x will exit\n", pthread_self());
            pthread_exit(NULL);
        }
        // 执行任务
        // assert是调试的好帮手
        assert(pool->cur_queue_size != 0);
        assert(pool->queue_head != NULL);
        
        // 等待队列长度减去1,并取出链表中的头部元素,操作完毕释放锁
        printf("thread 0x%x is starting to work\n", pthread_self());
        pool->cur_queue_size--;
        CThread_worker *worker = pool->queue_head;
        pool->queue_head = pool->queue_head->next;
        pthread_mutex_unlock(&(pool->queue_lock));
 
        // 调用回调函数,执行任务
        (*(worker->process))(worker->arg);
        free(worker);
        worker = NULL;
    }
    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) {
    // 初始化线程池,创建3个线程
    pool_init(3);
 
    // 往任务队列中加入10个任务
    int *workingnum = (int *) malloc (sizeof(int) * 10);
    int i = 0;
    for (i = 0; i < 10; i++) {
        workingnum[i] = i;
        pool_add_worker(myprocess, &workingnum[i]);
    }
    // 等待所有的任务执行完毕
    sleep(5);
    // 销毁线程池
    pool_destroy();
 
    free(workingnum);
    return 0;
}

结果如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值