多线程编程-线程池技术--哈哈哈哈哈哈哈哈

多线程编程-线程池技术

2017年10月21日 22:36:49 阅读数:226

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_35692628/article/details/78307089

序言

学习线程池技术的原理与实现。

1. 什么是线程池

  • 线程池就是一堆已经创建好的线程,初始都处于空闲等待状态,当有新的任务需要处理的时候,就从这个池子里取一个空闲等待的线程来处理该任务,当处理完成了就再次把该线程放回池中,供后面的任务使用。

  • 当池子里的线程全部处于忙碌状态时,线程池中没有可用的空闲等待线程,此时可以通知任务线程池忙稍后再试,或者创建一个新的线程并放入池中。

2. 为什么需要线程池

  • 需求:

    • 目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。
  • 传统处理方式:

    • 传统多线程方案中我们采用的服务器模型是:接收到请求,创建线程,由该线程执行该任务,任务执行完毕线程退出。是“即时创建,即时销毁”的策略。
    • 相比创建进程的方式,创建线程时间已经大为缩短,但是如果请求频繁的时候服务器就会处于频繁创建线程、频繁销毁线程的状态
  • 线程池技术引入

    • 如果一个程序每处理一个任务都需要创建一个线程来处理,假设创建线程的时间为T1,任务执行的时间为T2,线程销毁的时间为T3,那么线程的有效使用时间率为 T2 / (T1+T2+T3),如果任务执行的时间非常短,那么线程的使用效率就会非常低,这对高并发的服务器性能来说是不能接受的,所以需要引入线程池概念

3. 线程池工作原理

  • 线程池工作原理

    • [1] 创建若干线程,放入线程池
    • [2] 任务达到时,从线程池中取空闲线程
    • [3] 取得空闲线程,立即进行任务处理
    • [4] 如果没有取得空闲线程,创建一个线程并放入线程池中,执行[3]
    • [5] 如果创建失败或者线程池已满,根据设计策略选择返回错误或将任务放入待处理队列
    • [6] 销毁线程池
  • 线程同步问题:

    • 互斥锁、条件变量、信号量都可以用于线程同步。互斥锁 只能轮询去处理数据,可能造成空转而浪费CPU资源;此外,并发编程建议能使用互斥锁的地方就不要使用 信号量

    • 可以使用 互斥锁 + 条件变量 来解决同步问题,也可利用 多元信号量 进行线程分配设计

  • 线程分配设计方式一: 互斥锁 + 条件变量

    • 本文例子就是采用这种方式实现,每次只有单个线程执行待执行任务

    • 采用多元信号量的方式可实现多个线程实现多个任务的处理

  • 线程分配设计方式二: 多元信号量

    • 线程池中每个线程都是等价的。

    • 用线程信号量控制子线程和任务的分配问题。设置一个多元信号量来表示任务队列中的任务资源。

    • 每个线程都会处于死循环中,每轮循环首先等待一个任务资源信号量,当等到之后互斥地从任务队列中选取一个任务结点,任务结点中记录着该任务所要执行的函数指针和参数。然后子线程开始执行该任务,执行完之后释放一个信号量并进入下一轮循环。当信号量小于1时,子线程将会阻塞。

      说明:

      • [1] 因为一个任务由一个线程执行,所以要看哪个线程能够获得对应的信号量资源。
      • [2] 任务队列由双向链表构造,每个结点包含一个任务的函数指针和参数指针

4. 线程池的组成

  • 一个简单的线程池有下列组件

    • [1] 线程池管理器:用于创建并管理线程池

    • [2] 任务队列:线程池的概念具体到实现则可能是队列链表之类的数据结构,其中保存执行线程。用于存放没有处理的任务,提供一种缓冲机制

    • [3] 任务接口:将线程执行的任务抽象出来,每个任务必须实现的接口,以供工作线程调度任务的执行

    • [4] 工作线程:线程池中实际执行的线程

5. 线程池的实现:简单方案

// C语言实现方案
// 功能:创建线程池,添加任务,调用空闲线程处理任务,所有任务处理完销毁线程池

/* 基本设定
    任务队列:链表实现
    任务接口:回调函数,任务计数
    线程池管理器:结构体实现
    工作线程:显示当前哪个线程在处理哪个任务
*/

/* 详细代码 */
//主要参考:http://blog.csdn.net/zouxinfox/article/details/3560891

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <windows.h>

/* 任务队列 */
//元素:函数,参数指针
typedef struct job
{
    void* (*myprocess) (void* arg); /*任务回调函数*/
    void* arg;                     /*回调函数的参数*/
    struct job* next;              /*任务队列链表*/
} CThread_job;

/*回调函数*/
static void* myprocess(void* arg)
{
    printf("threadid: %x, working on task %d\n", pthread_self(), *(int*)arg);      //pthread_self(void)获取线程ID
    Sleep(1);
    return(NULL);
}

/*线程池结构*/
typedef struct thread_pool
{
    pthread_mutex_t mutex;      /*互斥锁 */
    pthread_cond_t cond;        /*条件变量 */

    CThread_job* queue_head;    /*线程池的任务队列*/

    int shutdown;               /*是否摧毁线程池 0:不摧毁 1:摧毁 */
    pthread_t* threadid;        /*线程ID数组*/
    int max_thread_num;         /*线程池最大线程数*/
    int cur_queue_size;         /*任务队列中待运行任务数目*/
} CThread_pool;

/*线程函数*/
void* thread_routine(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->mutex), NULL);       //互斥锁初始化函数
    pthread_cond_init(&(pool->cond), NULL);         //条件变量初始化函数

    pool->queue_head = NULL;

    pool->max_thread_num = max_thread_num;

    pool->shutdown = 0; /*0打开1关闭*/

    pool->cur_queue_size = 0;

    pool->threadid = (pthread_t*)malloc(max_thread_num * sizeof (pthread_t));  //线程ID数组

    /*创建工作线程*/
    int i = 0;
    for (i = 0; i < max_thread_num; ++i)
    {
        pthread_create(&(pool->threadid[i]), NULL, thread_routine, NULL);       //创建相同线程
    }
}

/*将任务加入队列*/
int pool_add_job(void* (*myprocess) (void* arg), void* arg)
{
    /*构造一个新任务*/
    printf("thread pool add task arg: %d\n", *(int*)arg);
    CThread_job* newjob = (CThread_job*)malloc(sizeof(CThread_job));            //新建任务节点
    newjob->myprocess = myprocess;
    newjob->arg = arg;
    newjob->next = NULL;

    /* 线程池的状态被多个线程共享,操作之前需要加锁 */
    pthread_mutex_lock(&(pool->mutex));                                         //上锁,线程池资源绑定执行

    /*将任务加入到任务队列中,也就是链表末端*/
    CThread_job* job = pool->queue_head;
    if (job != NULL)
    {
        while (job->next != NULL)
            job = job->next;
        job->next = newjob;                                                     //链表新节点
    }
    else
    {
        pool->queue_head = newjob;
    }

    /*是否需要唤醒线程*/
    int exeSignal = 0;
    if (pool->cur_queue_size == 0)
        exeSignal = 1;

    pool->cur_queue_size += 1;

    pthread_mutex_unlock(&(pool->mutex));

    /*需要叫醒工作线程*/
    if (exeSignal)
        pthread_cond_signal(&(pool->cond));

    return 0;
}

/*销毁线程池*/
int pool_destroy()
{
    printf("pool destroy now\n");

    /*启用关闭开关*/
    if (pool->shutdown)
        return -1; /*防止两次调用*/
    pool->shutdown = 1;

    /*唤醒所有等待线程*/
    pthread_cond_broadcast(&(pool->cond));

    /*阻塞等待线程退出回收资源,还有另一种办法就是线程分离*/
    int i;
    for (i = 0; i < pool->max_thread_num; ++i)
        pthread_join(pool->threadid[i], NULL);
    free(pool->threadid);
    pool->threadid = NULL;

    /*销毁任务队列*/
    CThread_job* head = NULL;
    while (pool->queue_head != NULL)
    {
        head = pool->queue_head;
        pool->queue_head = pool->queue_head->next;
        free(head);
        head = NULL;
    }

    /*销毁互斥锁与条件变量*/
    pthread_mutex_destroy(&(pool->mutex));
    pthread_cond_destroy(&(pool->cond));

    free(pool);
    pool = NULL;
    printf("pool destroy end\n");
    return 0;
}

/*工作线程函数*/
void* thread_routine(void* arg)
{
    printf("starting threadid: %x\n", pthread_self());

    for (; ;)
    {
        /* 访问线程池之前需要加锁 */
        pthread_mutex_lock(&(pool->mutex));
        /*任务队列为空时wait唤醒,当销毁线程池时跳出循环*/
        while (pool->cur_queue_size == 0 && !pool->shutdown)
        {
            printf("threadid: %x is waiting\n", pthread_self());
            pthread_cond_wait(&(pool->cond), &(pool->mutex));
        }

        /*线程池要销毁了*/
        if (pool->shutdown)             //pthread_cond_broadcast()之后,pool->shutdown置1,销毁线程
        {
            pthread_mutex_unlock(&(pool->mutex));
            printf("threadid: %x will exit\n", pthread_self());
            pthread_exit(NULL);
        }

        /*开始执行任务*/
        printf("threadid: %x is starting to work\n", pthread_self());

        /*等待队列长度减去1,并取出链表中的头元素*/
        pool->cur_queue_size -= 1;
        CThread_job* job = pool->queue_head;
        pool->queue_head = job->next;
        pthread_mutex_unlock(&(pool->mutex));

        /*调用回调函数,执行任务*/
        (*(job->myprocess)) (job->arg);
        free(job);
        job = NULL;
    }
    return(NULL);
}


/*测试*/
int main(int argc, char const *argv[])
{
    pool_init(3); /*创建n个线程*/

    /*添加n个任务*/
    int* jobNum = (int*)malloc(sizeof(int) * 10);       /* 一定要动态创建 */
    int i;
    for (i = 0; i < 5; ++i)
    {
        jobNum[i] = i;
        pool_add_job(myprocess, &jobNum[i]);
    }

    Sleep(5); /*等待所有任务完成*/

    pool_destroy(); /*销毁线程池*/
    free(jobNum);
    jobNum = NULL;
    return 0;
}

线程池的实现:线程池中增加/删除线程

//主要参考:http://blog.csdn.net/u014453898/article/details/53764720

/* 往线程池中增加线程 */

int add_thread(CThread_pool *pool, unsigned int add_threads)
{
    if(add_threads == 0)
        return 0;

    //total_threads: 总线程数,用于循环创建线程时使用
    unsigned int total_threads = pool->max_thread_num + add_threads;

    //actual_add: 为实际上增加的线程数
    int actual_add = 0;

    //创建新线程
    int i;
    for(i = pool->max_thread_num; i < total_threads; i++)
    {
        //创建线程,并判断是否创建成功,失败提示并退出该函数
        if(pthread_create(&(pool->threadid[i]), NULL, thread_routine, NULL) != 0)
        {
            perror("add threads failed!");
            if(actual_add == 0)
                return -1;
        }
        actual_add++;
    }

    //计算增加指定数目的线程后,线程池活动线程数加上实际成功增加的线程数
    pool->max_thread_num += actual_add;
    return actual_add;
}


/* 从线程池中删除线程 */
int remove_thread(CThread_pool *pool,unsigned int removing_threads)
{
    if(removing_threads == 0)
        return 0;

    int remain_threads = pool->max_thread_num - removing_threads;
    remain_threads = remain_threads > 0 ? remain_threads : 1;
    int i;
    for(i = pool->max_thread_num - 1; i > remain_threads - 1; i--)
    {
        errno = pthread_cancel(pool->threadid[i]);              //删除线程是按照线程创建的顺序来删的
        if(errno != 0)                                          //pthread_cancel成功返回0,失败返回错误编号,分立即取消和在可取消点取消
        {
            break;
        }
    }
    if(i == pool->max_thread_num - 1)
        return -1;
    else
    {
        pool->max_thread_num = i + 1;
        return pool->max_thread_num;
    }
}

6. 线程池的C++实现

猜你喜欢

转载自blog.csdn.net/lusic01/article/details/83539782