1.线程池
我们都知道,线程是进程的一个执行分支
操作系统上 以现场为单位进行调度
=》理论上而言 一个进程拥有的线程越多 其处理效率越高
但是 实际上 收操作系统,硬件设备等影响
一个进程创建的线程数量和处理效率并不是成正比(线性关系)
因此 一般情况下 线程的创建不能 "为所欲为"
之前的代码 如果创建成千上万个线程同时拷贝
那么系统就会卡死
因此为了解决这种BUG 参考C++,引入了线程池的概念
=》C没有线程池
2.线程池的原理 生产者与消费者模型
生产者
负责产生需要处理的数据
消费者
负责消费数据
有一个任务队列
生产者负责添加任务
消费者负责处理任务
当任务队列中的任务过多的时候
需要增加线程去处理任务
当任务队列中的任务不够的时候
需要减少线程,减少消耗
生产者
负责则添加任务
比如 上一个程序中 读到一个普通文件的时候
负责将普通文件入队
线程池
if(队列不为空)
{
唤醒一个队列去执行
}
消费者
等待被唤醒
唤醒了以后 出队节点并处理任务
去复制入队的普通文件
消费者正常情况下是不会死亡 除非任务队列
中的任务太少了,才会取消
void *thrad_fun(void *arg)
{
while(1)
{
等待任务来了 被唤醒
处理任务
}
}
模板代码:cp.c thread_pool.c thread_pool.h
thread_pool.h
#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <pthread.h>
#define MAX_WAITING_TASKS 1000
#define MAX_ACTIVE_THREADS 20
#define BUFSIZE 100
#define PATHSIZE 100
// "task": 指令的序列
// "函数内部": 指令封装在函数内.
// "task" -> 一个函数.
// "task" 提交,是不是马上就执行呢?
// 需要保存这个函数,好让后面的线程去执行它
// "保存函数" -> 函数指针.
// 函数指针有什么用? 保存一个函数的地址,
// 因为现在不调用它,等到后面再去调用它. callback
// call me back
// callback回调.:回过头来,调用.
struct task//任务结点
{
void *(*do_task)(void *arg); //函数指针,指向任务要执行的函数 .
void *arg; //一个指针,任务执行函数时作为函数的参数传入
struct task *next;
};
typedef struct thread_pool//线程池头结点
{
pthread_mutex_t lock; //互斥锁,用来保护这个"线程池",就是保护这个链表的
pthread_cond_t cond; // 有任务的条件
bool shutdown; //是否退出。
struct task *task_list;//任务链表,即指向第一个任务结点
pthread_t *tids;//指向线程ID的数组,因为我可能会创建多个线程。
//size = n * sizeof(pthread_t)
unsigned int max_waiting_tasks;//表示最大的执行的任务数
unsigned int waiting_tasks; //目前正在链表上的任务数,即待执行任务
unsigned int active_threads; //正在服役的线程数
}thread_pool;
/*
func:初始化一个线程池
param:pool 一个thread_pool的指针 要有指向的空间
threads_number:你初始要有几个活动的线程
return 成功返回true 失败返回false
*/
bool init_pool(thread_pool *pool, unsigned int threads_number);
/*
func:增加任务
param:pool 一个thread_pool的指针 要有指向的空间
do_task:你要执行的函数是哪一个
task:要执行的函数的参数
return 成功返回true 失败返回false
*/
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);
/*
func:增加线程 增加消费者数量
param:pool 一个thread_pool的指针 要有指向的空间
additional_threads_number 你要添加几个线程
return 成功返回添加的线程数 失败返回-1
*/
int add_thread(thread_pool *pool, unsigned int additional_threads_number);
/*
func:删除线程
param:pool 一个thread_pool的指针 要有指向的空间
removing_threads_number 你要删除几个线程
return 成功返回取消的线程数 失败返回-1
/~~~成功返回剩余的线程数量
*/
int remove_thread(thread_pool *pool, unsigned int removing_threads_number);
/*
func:销毁线程池
param:pool 一个thread_pool的指针 要有指向的空间
*/
bool destroy_pool(thread_pool *pool);
void *routine(void *arg);//任务执行函数
#endif
thread_pool.c
#include "thread_pool.h"
void handler(void *arg)
{
pthread_mutex_unlock((pthread_mutex_t *)arg);//解锁
}
void *routine(void *arg)//任务执行函数
{
#ifdef DEBUG
printf("[%u] is started.\n",
(unsigned)pthread_self());
#endif
thread_pool *pool = (thread_pool *)arg;//参数其实是个线程池头结点
struct task *p;//指向我要执行的任务
while(1)
{
/*
** push a cleanup function handler(), make sure that
** the calling thread will release the mutex properly
** even if it is cancelled during holding the mutex.
**
** NOTE:
** pthread_cleanup_push() is a macro which includes a
** loop in it, so if the specified field of codes that
** paired within pthread_cleanup_push() and pthread_
** cleanup_pop() use 'break' may NOT break out of the
** truely loop but break out of these two macros.
** see line 56 below.
*/
//================================================//
//pthread_cleanup_push 和 pthread_cleanup_pop是配套使用的
//从push开始到pop之间 只要线程中断 不管什么原因 都会去执行push注册了的函数
//这样做的意思是防止线程意外终止 导致死锁
pthread_cleanup_push(handler, (void *)&pool->lock);//注册一个线程清理函数,防止死锁
上锁
pthread_mutex_lock(&pool->lock);
//================================================//
// 1, no task, and is NOT shutting down, then wait
while(pool->waiting_tasks == 0 && !pool->shutdown)
{
条件变量进行等待
pthread_cond_wait(&pool->cond, &pool->lock);//没任务但线程池没有关的时候会阻塞在这里,唤醒的地方有两个
//一个是在添加任务时,另一个是在销毁线程池时
}
// 2, no task, and is shutting down, then exit
if(pool->waiting_tasks == 0 && pool->shutdown == true)
{
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL); // CANNOT use 'break';
}
// 3, have some task, then consume it
执行的任务task p
p = pool->task_list->next;//第一个任务结点并没有赋值,所以可以跳过它
pool->task_list->next = p->next;//更新pool->task_list->next
pool->waiting_tasks--;
上面是任务节点task的取出
//================================================//
pthread_mutex_unlock(&pool->lock);
pthread_cleanup_pop(0);//退出带锁结束的保护
//================================================//
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); //
执行线程函数
(p->do_task)(p->arg);//执行任务
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
????为什么要修改属性,从默认到可以取消到不可以取消,改的是哪一个线程的属性
修改是目前的线程的属性,不是执行任务这个线程,防止执行任务时,线程池这个线程被取消
p->next = NULL;
free(p);//释放掉以被执行的任务 结点的空间
}
pthread_exit(NULL);
}
/*
init_pool:初始化一个线程池
@pool: 指针。指向要初始化的纯种池
@threads_number:线程里常驻线程数
*/
bool init_pool(thread_pool *pool, unsigned int threads_number)//初始化线程池链表,并一个创建线程来运行线程池管理函数
{
初始化一个线程互斥锁
pthread_mutex_init(&pool->lock, NULL);
pthread_cond_init(&pool->cond, NULL);
pool->shutdown = false;//开机
//创建第一个任务结点并用task_list指向它,注意第一个任务结点并没有赋值,why?
pool->task_list = malloc(sizeof(struct task));
pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);
????上面的宏定义20是指这个数组长度20吗?可以存放20个ID??
是,设置这个线程池能存放20个线程的ID
if(pool->task_list == NULL || pool->tids == NULL)
{
perror("allocate memory error");
return false;
}
pool->task_list->next = NULL;
pool->max_waiting_tasks = MAX_WAITING_TASKS;
pool->waiting_tasks = 0;
unsigned int threads_number 初始要有几个活动的线程
pool->active_threads = threads_number;
int i;
//创建active_threads个线程来运行任务执行函数
for(i=0; i < pool->active_threads; i++)
{
线程id的存入
if(pthread_create(&((pool->tids)[i]), NULL,
routine, (void *)pool) != 0)
{
perror("create threads error");
return false;
}
//条件编译 测试
#ifdef DEBUG
printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
return true;
}
bool add_task(thread_pool *pool,void *(*do_task)(void *arg), void *arg)//添加任务
{
struct task *new_task = malloc(sizeof(struct task));//创建一个新的任务结点
if(new_task == NULL)
{
perror("allocate memory error");
return false;
}
new_task->do_task = do_task; //给新的任务结点赋值
new_task->arg = arg;
new_task->next = NULL;
//============ LOCK =============//
pthread_mutex_lock(&pool->lock);
//===============================//
if(pool->waiting_tasks >= MAX_WAITING_TASKS)//如果等待任务数量达到上限
{
pthread_mutex_unlock(&pool->lock);
fprintf(stderr, "too many tasks.\n");
free(new_task);
return false;//放弃添加,结束函数
}
struct task *tmp = pool->task_list;
while(tmp->next != NULL)
{
tmp = tmp->next;//找到最后一个结点
}
tmp->next = new_task;
pool->waiting_tasks++;
//=========== UNLOCK ============//
pthread_mutex_unlock(&pool->lock);
//===============================//
#ifdef DEBUG
printf("[%u][%s] ==> a new task has been added.\n",
(unsigned)pthread_self(), __FUNCTION__);
#endif
????唤醒条件变量的作用是什么?提醒开始工作?
唤醒线程,系统会自动分配一个被唤醒线程进行工作,不需要指定线程工作
pthread_cond_signal(&pool->cond);//唤醒条件变量
return true;
}
int add_thread(thread_pool *pool, unsigned additional_threads)//创建活动线程
{
if(additional_threads == 0)
{
return 0;
}
//最终你有几个活动的线程
unsigned total_threads = pool->active_threads + additional_threads;
int i, actual_increment = 0;
for(i = pool->active_threads;
i < total_threads && i < MAX_ACTIVE_THREADS;
i++)
{
线程id存入
if(pthread_create(&((pool->tids)[i]),
NULL, routine, (void *)pool) != 0)//创建线程
{
perror("add threads error");
// no threads has been created, return fail
if(actual_increment == 0)
return -1;
break;
}
actual_increment++;
#ifdef DEBUG
printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
pool->active_threads += actual_increment;
return actual_increment;
}
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
if(removing_threads == 0)
return pool->active_threads;
int remaining_threads = pool->active_threads - removing_threads;//删除后剩余的线程
remaining_threads = remaining_threads > 0 ? remaining_threads : 1;//为什么要留1,why?线程池本身就是一个线程
int i;
for(i = pool->active_threads - 1; i > remaining_threads - 1; i--)//我们的目的就缓解硬件资源紧张而不是结束任务
{
//取消线程
errno = pthread_cancel(pool->tids[i]);
if(errno != 0)
break;
#ifdef DEBUG
printf("[%u]:[%s] ==> cancelling tids[%d]: [%u]...\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
if(i == pool->active_threads-1)
{
从开始就取消线程失败
return -1;
}
else
{
输出目前剩余的线程数
pool->active_threads = i+1;
return i+1;
}
}
bool destroy_pool(thread_pool *pool)
{
// 1, activate all threads
pool->shutdown = true;
pthread_cond_broadcast(&pool->cond);
上面的播报是什么作用?唤醒所有线程?怎么唤醒?对互斥锁有没有需求,为什么没有?自带?
自带的函数,会唤醒线程池里面所有的线程
// 2, wait for their exiting
int i;
for(i=0; i < pool->active_threads; i++)
{
循环结束线程 这个线程id是什么时候存进去的?
初始化一个线程池的时候,和增加线程数量的时候
errno = pthread_join(pool->tids[i], NULL);
if(errno != 0)
{
printf("join tids[%d] error: %s\n",
i, strerror(errno));
}
}
// 3, free memories
free(pool->task_list);
free(pool->tids);
free(pool);
return true;
}
本线程池可以增加到20个线程,目前里面初始化了10个线程,可以进行的任务数量是1000个
cp.c (简单的运用此线程池)
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <libgen.h>
#include <pthread.h>
#include "thread_pool.h"
// char *dirname(char *path);
// char *basename(char *path);
//指向一个"线程池"
thread_pool *pool = NULL;
//拷贝普通文件
struct cp_files
{
char file1[512];
char file2[512];
};
执行线程的函数 参数是结构体cp
void* cp_files_routine(void *data)
//void cp_file(char *file1, char *file2)
{
char *file1, *file2;
int fd1, fd2;
int ret;
char buf[1024];
/取出结构体里面的两个文件名字
struct cp_files *p = (struct cp_files *)data;
file1 = p->file1;
file2 = p->file2;
//pthread_detach(pthread_self());
fd1 = open(file1, O_RDONLY);
if (fd1 == -1)
{
printf("%s \n", file1);
perror("open error");
//return NULL;
goto cp_return;
}
fd2 = open(file2, O_WRONLY | O_CREAT | O_TRUNC, 0777);
if (fd2 == -1)
{
printf("%s \n", file2);
perror("open error");
//close(fd1);
//return NULL;
goto cp_return;
}
while (1)
{
ret 是实际读取的字节数
ret = read(fd1, buf, 1024);
if (ret == 0)
{
break;
}
else if (ret > 0)
{
int w = write(fd2, buf, ret);
if (w != ret)
{
}
}
else
{
perror("read error");
break;
}
}
cp_return:
free(data);
close(fd1);
close(fd2);
return NULL;
}
//拷贝目录
void cp_dir(char *path1, char *path2)
{
struct dirent * dirp = NULL;
DIR *dir = opendir(path1);
if (dir == NULL)
{
perror("opendir error");
return ;
}
char path2_name[512];
char *p_name = basename(path1);
sprintf(path2_name, "%s/%s", path2, p_name);
创建文件2 ,给所有权限
mkdir(path2_name, 0777);
path2 = path2_name;
while (dirp = readdir(dir))
{
///?????strcmp比较什么?
如果读到的文件名字是目录项则继续往下面读,后面递归
if (strcmp(dirp->d_name, ".") == 0 ||
strcmp(dirp->d_name, "..") == 0)
{
continue;
}
char filename[512];
sprintf(filename, "%s/%s", path1, dirp->d_name);
struct stat st;
lstat(filename, &st);
/普通文件 ||或 符号链接文件(快捷方式)
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
{
char file[512];
sprintf(file, "%s/%s", path2, dirp->d_name);
struct cp_files* cp = malloc(sizeof(*cp));
复制filename到cp->file1 复制到结构体cp里面
strcpy(cp->file1, filename);
strcpy(cp->file2, file);
//cp_file( filename, file);
给链表节点增加任务
add_task(pool, cp_files_routine, cp);
}
为 目录项
else if (S_ISDIR(st.st_mode))
{
char pathname[512];
sprintf(pathname, "%s/%s", path2, dirp->d_name);
mkdir(pathname, 0777);
递归函数 递归本身
cp_dir(filename, pathname);
}
}
closedir(dir);
return ;
}
// cp f/d1 f/d2
int main(int argc, char *argv[])
{
int ret;
struct stat st1, st2;
if( stat(argv[1], &st1) || stat(argv[2], &st2) )
{
printf("-----\n");
perror("stat error");
return ;
}
if (S_ISREG(st1.st_mode) && S_ISREG(st2.st_mode))
{
//cp_file(argv[1], argv[2]);
}
//定义一个"线程池"并初始化它
pool = malloc(sizeof(*pool));
初始化线程池
init_pool(pool, 10);
if (S_ISDIR(st1.st_mode) && S_ISDIR(st2.st_mode))
{
/cp目录中的文件
cp_dir(argv[1], argv[2]);
}
销毁线程池
destroy_pool( pool);
return 0;
}
线程池函数结构:
总体:
两个结构体,六个函数
结构体: 任务节点
函数指针 线程函数 do_task
一个指针 上面函数的参数的传入 arg
指向下一个任务节点
结构体: 线程池头结点
上锁 保护线程池 lock
条件 用于唤醒线程 cond
退出线程池使用标志 shutdown
任务节点组成的链表 task_list
存放线程ID的数组 tids
线程池容量:
最大进行的任务数 max_waiting_tasks
正在链表上的任务数量 waiting_tasks
正在运行的线程数 active_threads (线程ID的数组中的数量)
函数:(以下函数的参数部分选自上面的模板)
初始化 线程池 init_pool(pool ,10);
增加任务数 add_task(pool,cp_files_routine,cp); 【1.线程池 2.线程函数 3.参数】
增加线程 add_thread(pool,10);
删除线程 remove_thread(pool,2);
销毁线程池 destroy_pool(pool);
任务执行函数 routine();
各个函数的具体结构:
init_pool(pool,10);
初始化结构线程池:
上锁,条件变量,
创建一个线程执行函数:
.......
add_task(pool,cp_files_routine,cp);
初始化 new_task
上锁 线程池
判断任务数量---> temp :task_list的尾结点 new_task->增加上去
解锁 线程池
唤醒条件变量(自动分配线程执行任务task)
add_thread(pool,10);
安全判断
最终活动的线程
从新的线程数增加创建新线程
修改活动线程数量
remove_thread(pool,2);
安全判断
最终活动的线程数
安全监测活动线程数量
从最后一个开始取消相应的线程
destrey_pool(pool);
修改是否退出标志
播报,唤醒所有线程
按照ID依次结束线程for循环
.....
routine();
上锁 判断情况:
1.等待使用线程池
2.退出使用线程池
3.执行使用线程池: 取出任务节点:task_list--; ---->解锁
执行线程函数
.....