Linux中线程池简介与实现示例

由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间,即“浪费”服务器的硬件资源,以换取其运行效率。这就是池的概念。

池是一组资源的集合,这组资源在服务器启动之初就被创建并初始化,这称为静态资源分配。

当服务器进入正式运行阶段,即开始处理客户请求的时候,如果它需要相关的资源,就可以直接从池中获取,无需动态分配。很显然,直接从池中取得所需资源比动态分配资源的速度要快得多,因为分配系统资源的系统调用都是很耗时的。

当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用来释放资源。从最终效果来看,池相当于服务器管理系统资源的应用设施,它避免了服务器对内核的频繁访问。提高了效率。

池可以分为很多种,常见的有进程池,线城池,内存池。

内存池

内存池是一种内存分配方式。通常我们直接使用new、malloc等系统调用申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。

线程池

在面向对象程序编程中,对象的创建与析构都是一个较为复杂的过程,较费时间,所以为了提高程序的运行效率尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。 
所以我们可以创建一个线程池,预先构造一些线程,要用的时候就直接调用,用完之后再把线程归还给线程池,省下创建删除线程的时间,不过当然就需要额外的开销了。 

线程池的应用

线程池主要用于 
1、需要大量的线程来完成任务,且完成任务的时间比较短。 
WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。 
因为单个任务小,而任务数量巨大,一个热门网站的点击次数会很多。 
但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

2、对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。

线程池的好处在于减少了频繁创建线程和销毁线程的时间,提高了效率。

线程池的实现示例

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;
}

结果如下:

猜你喜欢

转载自blog.csdn.net/vanturman/article/details/84978396