线程池 ThreadPool的简单实现

534 阅读6分钟
/*
 * 整体思路:
 * 首先创建好线程池,这里主要是创建N个线程并运行,每一个线程都通过pthread_cond_wait阻塞运行,直至收到pthread_cond_signal,条件变量得到
 * 满足,得以从任务队列中获取一个任务并调用任务中的回调函数进行处理。
 * 然后,其他线程不停的构造任务结构,并放入任务队列中。
 * 这样,线程池作为消费者不停的从任务队列中取任务,其他线程不停的作为生产者往任务队列中放任务。
 */

/*
 * 重点讲一下pthread_cond_wait和pthread_cond_signal的应用与理解
 * 参考链接:https://blog.csdn.net/coolwriter/article/details/80667345
 
 * 使用pthread_cond_wait方式如下:

    pthread _mutex_lock(&mutex)
    while或if(线程执行的条件不成立)
        pthread_cond_wait(&cond, &mutex);

    线程执行
   pthread_mutex_unlock(&mutex);
 * 需要解释的有两点,为什么要加锁,以及为什么可以使用while和if !!!
 * 首先解释第一点,有两个方面,线程在执行的部分访问的是进程的资源,有可能有多个线程需要访问它,
 * 为了避免由于线程并发执行所引起的资源竞争,所以要让每个线程互斥的访问公有资源,但是细心一下就会发现,
 * 如果while或者if判断的时候,不满足线程的执行条件,那么线程便会调用pthread_cond_wait阻塞自己,但是它持有的锁怎么办呢,
 * 如果他不归还操作系统,那么其他线程将会无法访问公有资源。这就要追究一下pthread_cond_wait的内部实现机制,
 * 当pthread_cond_wait被调用线程阻塞的时候,pthread_cond_wait会自动释放互斥锁。
 * 释放互斥锁的时机是什么呢:是线程从调用pthread_cond_wait到操作系统把他放在线程等待队列之后,这样做有一个很重要的原因,
 * 就是mutex的第二个作用,保护条件。想一想,线程是并发执行的,如果在没有把被阻塞的线程A放在等待队列之前,就释放了互斥锁,
 * 这就意味着其他线程比如线程B可以获得互斥锁去访问公有资源,这时候线程A所等待的条件改变了,但是它没有被放在等待队列上,
 * 导致A忽略了等待条件被满足的信号。倘若在线程A调用pthread_cond_wait开始,到把A放在等待队列的过程中,都持有互斥锁,
 * 其他线程无法得到互斥锁,就不能改变公有资源。这就保证了线程A被放在等待队列上之后才会有公有资源被改变的信号传递给等待队列。对于这点apue给出的解释:
 *
 * The mutex passed to pthread_cond_wait protects the condition.The caller passes it locked to the function, 
 * which then atomically places the calling thread on the list of threads waiting for the condition and unlocks the mutex.
 * This closes the window between the time that the condition is checked and the time that the thread goes to sleep waiting for
 * the condition to change, so that the thread doesn't miss a change in the condition. When pthread_cond_wait returns, the mutex is again locked.
 *
 * 接下来讲解使用while和if判断线程执行条件是否成立的区别。
 * 一般来说,在多线程资源竞争的时候,在一个使用资源的线程里面(消费者)判断资源是否可用,
 * 如果不可用则调用pthread_cond_wait,在另一个线程里面(生产者)如果判断资源可用的话,则调用pthread_cond_signal发送一个资源可用信号。
 * 但是在wait成功之后,资源就一定可以被使用么,答案是否定的,如果同时有两个或者两个以上的线程正在等待此资源,wait返回后,
 * 资源可能已经被使用了,在这种情况下,应该使用:

 *  while(resource == FALSE)
        pthread_cond_wait(&cond, &mutex);
 * 如果只有一个消费者,那么使用if也不可以,因为有虚假唤醒的情况存在。参考:https://www.zhihu.com/question/271521213
 * 分解pthread_cond_wait的动作为以下几步:
 * 1,线程放在等待队列上,解锁
 * 2,等待 pthread_cond_signal或者pthread_cond_broadcast信号之后去竞争锁
 * 3,若竞争到互斥索则加锁。
 * 上面讲到,有可能多个线程在等待这个资源可用的信号,信号发出后只有一个资源可用,但是有A,B两个线程都在等待,B比较速度快,获得互斥锁,
 * 然后加锁,消耗资源,然后解锁,之后A获得互斥锁,但他回去发现资源已经被使用了,它便有两个选择,一个是去访问不存在的资源,另一个就是继续等待,
 * 那么继续等待下去的条件就是使用while,要不然使用if的话pthread_cond_wait返回后,就会顺序执行下去。
 
 * 下面来讲一下:pthread_cond_wait和pthread_cond_singal是怎样配对使用的:

    等待线程(或者叫消费者线程)的使用:

    1. pthread_cond_wait前要先加锁
    2. pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活
    3. pthread_cond_wait被激活后会再自动加锁
	
    激活线程(或者叫生产者线程)的使用:
	
    1. 加锁(和等待线程用同一个锁)
    2. 解锁(这一步放在pthread_cond_signal之前是因为如果后解锁,其他线程收到信号获取不到锁,造成资源浪费,依然要等该线程解锁)
    3. pthread_cond_signal发送信号(发送信号前最好判断有无等待线程)
	
    激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部。
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <assert.h>

// 定义由线程池处理的任务的结构,内含任务的回调函数和相关的数据
typedef struct _Task {
    void (*task_callback)(void* arg);
    void* arg;
    struct _Task* next;
} Task;

// 定义线程池的结构
typedef struct {
    pthread_mutex_t queue_mutex; // 用于保护cond_var的互斥锁
    pthread_cond_t cond_var;     // 用于同步生产者消费者的condition_variable
    Task* queue_head;            // 任务队列头部
    int shutdown;
    pthread_t* threadids;        // 用于存放线程池中各个线程的标识符
    int max_thread_num;
    int cur_queue_size;          // 当前任务队列中的任务数量
}ThreadPool;

int pool_add_worker(void (*task_callback)(void* arg), void* arg);
void* thread_routine(void* arg);

static ThreadPool* pool = NULL;

// 线程池初始化,将所有的线程创建好并启动
void pool_init(int max_thread_num)
{
    pool = (ThreadPool*)malloc(sizeof(ThreadPool));
	
    pthread_mutex_init(&(pool->queue_mutex), NULL);
    pthread_cond_init(&(pool->cond_var), NULL);
	
    pool->queue_head = NULL;
    pool->max_thread_num = max_thread_num;
    pool->cur_queue_size = 0;
    pool->threadids = (pthread_t*)malloc(sizeof(pthread_t)*max_thread_num);
    pool->shutdown = 0;
    int i = 0;
    for(; i < max_thread_num; i++) {
		// 创建线程并启动,thread_routine 即为线程入口函数
        pthread_create(&pool->threadids[i], NULL, thread_routine, NULL);
    }
}

// 线程的入口函数
void* thread_routine(void* arg)
{
    printf("starting thread 0x%x\n", pthread_self());
    while(1) {
        pthread_mutex_lock(&(pool->queue_mutex));
        while(pool->cur_queue_size == 0 && !pool->shutdown) {
            printf("thread 0x%x is waiting\n", pthread_self());
            pthread_cond_wait(&(pool->cond_var), &(pool->queue_mutex));
        }

        if (pool->shutdown) {
            pthread_mutex_unlock(&(pool->queue_mutex));
            printf("thread 0x%x will exit\n", pthread_self());
            pthread_exit(NULL);
        }

        printf("thread 0x%x is starting to work\n", pthread_self());

        assert(pool->cur_queue_size != 0);
        assert(pool->queue_head != NULL);

        pool->cur_queue_size--;
        Task* worker = pool->queue_head;
        pool->queue_head = worker->next;
        pthread_mutex_unlock(&(pool->queue_mutex));

        // 调用回调函数,执行任务
        (*(worker->task_callback))(worker->arg);
        free(worker);
        worker = NULL;
    }
    pthread_exit(NULL); // 不应该走到这里
}

// 向线程池的任务队列中添加任务
int pool_add_worker(void (*task_callback)(void* arg), void* arg)
{
    Task* new_worker = (Task*)malloc(sizeof(Task));
    new_worker->task_callback = task_callback;
    new_worker->arg = arg;
    new_worker->next = NULL;
	
    pthread_mutex_lock(&(pool->queue_mutex));
	
    Task* cur = pool->queue_head;
    if (pool->queue_head) {
        while(cur->next) {
            cur = cur->next;
        }
        cur->next = new_worker;
    } else {
        pool->queue_head = new_worker;
    }
    pool->cur_queue_size++;
    
    pthread_cond_signal(&(pool->cond_var));
	pthread_mutex_unlock(&(pool->queue_mutex));
	
    return 0;
}

// 任务的回调函数
void task_callback(void* arg)
{
    printf("threadid is 0x%x, working on task %d\n", pthread_self(), *(int*)arg);
    sleep(1);
    return;
}

// 线程池销毁
int pool_destroy()
{
    if (pool->shutdown) {
        return -1; // 避免二次调用
    }

    pool->shutdown = 1;

    // 唤醒所有等待线程,线程池要销毁了
    pthread_cond_broadcast(&(pool->cond_var));

    // 阻塞等待线程退出,否则就成僵尸了
    int i;
    for (i = 0; i < pool->max_thread_num; i++) {
        pthread_join(pool->threadids[i], NULL);
    }
    free(pool->threadids);

    // 销毁任务等待队列
    Task* head = pool->queue_head;
    while(head) {
        Task* del = head;
        head = head->next;
        free(del);
    }

    pthread_mutex_destroy(&(pool->queue_mutex));
    pthread_cond_destroy(&(pool->cond_var));
    free(pool);
    pool = NULL;
    return 0;
}

int main()
{
    pool_init(3);

    int* working_num = (int*)malloc(sizeof(int) * 10);
    int i;
    for (i = 0; i < 10; i++) {
        working_num[i] = i;
        pool_add_worker(task_callback, &working_num[i]);
    }

    sleep(5);
    pool_destroy();

    free(working_num);
    return 0;
}