线程池基本概念理解以及代码解析应用
上文写了有关linux环境下c语言线程基础 linux线程池学习(一)——基础线程学习
这篇主要写我对在网上下的一个线程池源代码的理解。完整代码链接在本文最后。
源代码包含三个文件:(1)threadpool.h (2) threadpool.c (3) main.c
关于三个文件如何在linux环境下编译的我之前也写过 linux环境下运用makefile编译运行详解
.- - - - - - - - - - - - - - 如果你会编译这三个文件,请跳过下面这段- - - - - - - - - - - - - - -
首先,在这里用上面方法出现一个问题,编译还是报错,后来发现,因为我们这里文件中有头文件#include<pthread.h>
而这个头文件不是标准库文件,你下完这个文件放进默认头文件夹里后还需要在编译的时候附上链接 -pthread
出现问题:当没加-pthread在.c尾部时。编译时出现:
threadpool.o:在函数‘threadpool_init’中:
threadpool.c:(.text+0x197):对‘pthread_create’未定义的引用
threadpool.o:在函数‘threadpool_destroy’中:
threadpool.c:(.text+0x61e):对‘pthread_join’未定义的引用
collect2: error: ld returned 1 exit status
make: *** [threadpool] 错误 1
所以makefile文件内容修改后,编译成功,以下为makefile文件内容:
threadpool:threadpool.o main.o
gcc -o threadpool threadpool.o main.o
threadpool.o:threadpool.c threadpool.h
gcc -c threadpool.c -pthread
main.o:main.c threadpool.h
gcc -c main.c -pthread
clean:
rm threadpool threadpool.o main.o
正常编译后,我们进入正题- - - - - - - - - - -正文分割线- - - - - - - - - - - - - - - - - - - - - - -
一.线程池的理解
线程池是什么?
1. 使一种线程可以复用,执行完一个任务,并不被销毁,而是可以继续执行其他的任务的方法;
2. 在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到预设线程数thread_num后,就会把到达的任务放到缓存队列当中,缓冲队列值queue_max_num若满了,则阻止进入;
二.线程池代码理解
(1)threadpool.h主要是线程池结构体的定义和线程池相关函数的声明
struct job
{
void* (*callback_function)(void *arg); //线程回调函数
void *arg; //回调函数参数
struct job *next;
};
struct threadpool
{
int thread_num; //线程池中开启线程的个数
int queue_max_num; //队列中最大job的个数
struct job *head; //指向job的头指针
struct job *tail; //指向job的尾指针
pthread_t *pthreads; //线程池中所有线程的pthread_t
pthread_mutex_t mutex; //互斥信号量
pthread_cond_t queue_empty; //队列为空的条件变量
pthread_cond_t queue_not_empty; //队列不为空的条件变量
pthread_cond_t queue_not_full; //队列不为满的条件变量
int queue_cur_num; //队列当前的job个数
int queue_close; //队列是否已经关闭
int pool_close; //线程池是否已经关闭
};
//线程池初始化
struct threadpool* threadpool_init(int thread_num, int queue_max_num);
//向线程池中加任务(即需要被执行的回调函数)
int threadpool_add_job(struct threadpool *pool, void* (*callback_function)(void *arg), void *arg);
//销毁线程池
int threadpool_destroy(struct threadpool *pool);
//线程池中的线程函数
void* threadpool_function(void* arg);
(2) threadpool.c
主要用到的就是这个文件里的函数定义;
函数声明在threadpool.h
主要函数—— 线程池初始化
struct threadpool* threadpool_init(int thread_num, int queue_max_num)
{
struct threadpool *pool = NULL;
do
{
//给线程池结构体动态分配空间
pool = malloc(sizeof(struct threadpool));
if (NULL == pool)
{
printf("failed to malloc threadpool!\n");
break;
}
pool->thread_num = thread_num;
pool->queue_max_num = queue_max_num;
pool->queue_cur_num = 0;
pool->head = NULL;
pool->tail = NULL;
//初始化互斥锁,若初始化成功,即互斥锁为pool->mutex
if (pthread_mutex_init(&(pool->mutex), NULL))
{
printf("failed to init mutex!\n");
break;
}
//初始化队列标志
if (pthread_cond_init(&(pool->queue_empty), NULL))
{
printf("failed to init queue_empty!\n");
break;
}
if (pthread_cond_init(&(pool->queue_not_empty), NULL))
{
printf("failed to init queue_not_empty!\n");
break;
}
if (pthread_cond_init(&(pool->queue_not_full), NULL))
{
printf("failed to init queue_not_full!\n");
break;
}
//给线程池线程ID指针动态分配内存
pool->pthreads = malloc(sizeof(pthread_t) * thread_num);
if (NULL == pool->pthreads)
{
printf("failed to malloc pthreads!\n");
break;
}
pool->queue_close = 0;
pool->pool_close = 0;
int i;
//创建thread_num个线程(此时线程还没有运行)
/*重点解释下为什么这里的函数用线程函数threadpool_fun而不用回调函数:
我们知道,线程池里的一个线程可以先后处理多个不同的job,也就是我们这里的回调函数callback_function,如果使用回调函数,很明显,我们运行完一个job,线程就会关闭,这跟使用线程池的理念背道而驰,使用threadpool_fun是通过while结构循环使用线程,当当前job工作完后,自动检查线程池是否有排队队列等待处理,若有,则把线程池中的head job取出来,运行,队列数减一。若没有且线程池没有关闭,则继续等待队列不为空,若线程池关闭,则销毁该线程。*/
for (i = 0; i < pool->thread_num; ++i)
{
pthread_create(&(pool->pthreads[i]), NULL, threadpool_function, (void *)pool);
}
return pool;
} while (0);
return NULL;
}
其实个人认为最关键的就是这个函数,理解了这个函数就理解了C语言线程运行的整体步骤。
之后就是对线程池函数threadpool_function的定义,在上面重点解释里也提到了这个函数整体运行思路。
(3)main.c 该函数主要是通过对threadpool.c内函数threadpool_add_job(struct threadpool* pool, void* (*callback_function)(void *arg), void *arg)
的调用使用线程池多线程实现具体函数运行。在线程池初始化后直接通过向队列中添加job使线程池自动对队列中的job进行处理。