(使用线程池和epoll边沿非阻塞简单服务器的制作)
简单说明自己的情况
非科班,学习计算基础课程有段时间了,看了c++primer,apue,计算机操作系统,计算机网络等书,但发现自己陷入了一个误区,就是项目做得太少,看似懂了,实则什么也不明白。现在养成记录自己学习的习惯,(以前喜欢用纸写笔记),希望明年这个时候能够转行成功,不管结果如何,make it.
线程池基本原理
1、线性池设计的基本思路是分为管理线程和工作线程两部分,管理线程每隔一段时间去检测需要处理的任务占存活的工作线程比例,当比例超过一定值后(比如80%),创建线程,当比例低于一定值后(比如20%),销毁线程。线程池中的工作线程从任务中取出数据,(任务队列,执行任务)。
#ifndef __THREADPOOL_H_
#define __THREADPOOL_H_
const DEFAULT_TIME =10 /*10s检测一次*/
const MIN_WAIT_TASK_NUM = 10 /*如果queue_size > MIN_WAIT_TASK_NUM 添加新的线程到线程池*/
const DEFAULT_THREAD_VARY = 10 /*每次创建和销毁线程的个数*/
const int MAX_THREADS = 1024; //最大能创建的线程数
const int MAX_QUEUE = 65535; //队列任务最大值
typedef struct threadpool_t threadpool_t;
typedef struct {
void *(*function)(void *); /* 函数指针,回调函数 */
void *arg; /* 上面函数的参数 */
} threadpool_task_t; /* 各子线程任务结构体 */
//描述线程池的相关信息
struct threadpool_t
{
pthread_mutex_t lock; //用于锁定本结构体
pthread_mutex_t thread_counter; //记录忙状态线程数个数的锁
pthread_cond_t queue_not_full; //当任务队列满的时候,添加任务的线程阻塞,等待此条件变量
pthread_cond_t queue_not_empty; //任务列里不为空时,通知等待任务的线程
pthread_t *threads; //存放线程池中给个线程tid的数组
pthread_t adjust_tid; //存放管理线程的tid
threadpool_task_t *task_queue; //任务队列
int min_thr_num; //线程池最小线程的个数
int max_thr_num; //线程池最大线程的个数
int live_thr_num; //当前存活线程个数
int wait_exit_thr_num; //要销毁的线程的个数
int busy_thr_num; //忙状态线程的个数
int queue_front; //task_queue队列的下标
int queue_rear; //task_queue队列的尾
int queue_size; //task_queue队列实际任务的个数
int queue_max_size; //task_queue队列可容纳的任务数量上限
bool shutdown; //标志位,线程池使用状态,true或false
};
//创建线程
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size);
//添加线程
int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg);
//销毁线程
int threadpool_destroy(threadpool_t *pool);
//总线程个数
int threadpool_all_threadnum(threadpool_t *pool);
//忙状态线程个数
int threadpool_busy_threadnum(threadpool_t *pool);
//测试线程是否存活
int is_thread_alive(pthread_t tid);
//工作线程运行空间
void *threadpool_thread(void *threadpool);
//管理者线程运行空间
void *adjust_thread(void *threadpool);
//释放存储空间
int threadpool_free(threadpool_t *pool);
#endif // !_THREADPOLL_H
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include "threadpool.h"
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
{
threadpool_t *pool = nullptr;
do {
if (min_thr_num <= 0 || max_thr_num >= MAX_THREADS || queme_max_size > MAX_QUEUE || queme_max_size <= 0)
return nullptr;
if (!(pool = (threadpool_t*)malloc(sizeof(threadpool_t))))
break;
//初始化
pool->min_thr_num = min_thr_num;
pool->max_thr_num = max_thr_num;
pool->busy_thr_num = 0;
pool->live_thr_num = min_thr_num; //活着的线程
pool->queue_size = 0; //任务个数
pool->queue_max_size = queue_max_size;
pool->queue_front = 0;
pool->queue_rear = 0;
pool->queue_rear = 0;
pool->shutdown = false; //不关闭线程池
//根据最大线程上限数,给工作线程开辟空间,并清零
if (!(pool->threads = (pthread_t*)malloc(sizeof(pthread_t) *max_thr_num)))
break;
memset(pool->threads, 0, sizeof(pthread_t) *max_thr_num);
//队列开辟空间
if (!(pool->task_queue = (threadpool_task_t*)malloc(sizeof(threadpool_task_t)*queue_max_size)))
break;
//初始化互斥锁,条件变量
if (pthread_mutex_init(&(pool->lock), NULL) != 0
|| pthread_mutex_init(&(pool->thread_counter), NULL) != 0
|| pthread_cond_init(&(pool->queue_not_empty), NULL) != 0
|| pthread_cond_init(&(pool->queue_not_full), NULL) != 0)
break;
//启动min_thr_num 个工作线程
for (int i = 0; i < min_thr_num; i++)
{
if (pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool) != 0) //创建成功返回为0
{
threadpool_destroy(pool, 0);
break;
}
}
//启动管理者线程
if (pthread_create(&(pool->adjust_id), NULL, adjust_thread, (void*)pool))
{
threadpool_destroy(pool, 0);
break;
}
return pool;
} while (0);
threadpool_free(pool); //在创建线程代码调用失败时,释放poll的空间,避免内存泄漏
return nullptr;
}
//向线程池中添加一个任务
int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg) //第二个为回调函数,第三个参数为回调函数的参数
{
pthread_mutex_lock(&(pool->lock));
//为真时,队列已满,调用wait函数阻塞
while ((pool->queue_size == pool->queue_max_size) && (!pool->shutdown))
{
pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
}
if (pool->shutdown)
pthread_mutex_unlock(&(pool->lock));
//清空工作线程,调用回调函数,
if (pool->task_queue[pool->queue_rear].arg != NULL) {
free(pool->task_queue[pool->queue_rear].arg);
pool->task_queue[pool->queue_rear].arg = NULL;
}
//添加任务到队列
pool->task_queue[pool->queue_rear].function = function;
pool->task_queue[pool->queue_rear].arg = arg;
pool->queue_rear = (pool->queue_rear + 1) % pool->queue_front_max_size; //模拟环形队列
pool->queue_size++;
//添加任务后,队列不为空,唤醒线程池,等待处理任务的线程
pthread_cond_signal(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return 0;
}
//线程池中各个工作线程
void *threadpool_thread(void *threadpool)
{
threadpool_t *pool = (threadpool_t *)threadpool;
threadpool_task_t task;
while (1)
{
//刚创建出的线程,等待队列中有任务产生,否则阻塞等待任务队列中有任务产生在唤醒接收任务
pthread_mutex_lock(&(pool_->lock));
//queue_size == 0说明没有任务,调用wait阻塞在条件变量上,若有任务,跳过while
while ((pool->queue->size == 0) && (!pool->shutdown))
{
pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
//清除指定数目的空闲线程,如果要结束的线程个数大于0,结束线程
if (pool->wait_exit_thr_num > 0)
{
pool->wait_exit_thr_num--;
//如果线程池里的线程个数大于最小时可以结束当前线程
if (pool->live_thr_num > pool->min_thr_num)
{
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
}
}
//如果指定了true,要关闭线程池里的每一个线程,自行退出处理
if (pool->shutdown)
{
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL); //线程自己结束
}
//从任务队列里获取任务,是一个出队操作
task.function = pool->task_queue[pool->queue_front].function;
task.arg = pool->task_queue[pool->queue_front].arg;
pool->queue_front = (pool->queue_front_ + 1) % pool->queue_max_size; //模拟环形队列
pool->queue_size--;
//通知可以有新的任务添加进来
//pthread_cond_broadcast函数可以唤醒多个条件变量
pthread_cond_broadcast(&(pool->queue_not_full));
//执行任务
pthread_mutex_lock(&(pool->threads->counter)); //防止同时多个线程同时处理一个任务
(*(task.function))(task.arg); //执行回调函数
//任务结束处理
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num--; //处理掉一个任务,忙状态线程数减1
pthread_mutex_unlock(&(pool->thread_counter));
}
pthrad_exit(NULL);
}
//管理线程
void *adjust_thread(void* threadpool)
{
threadpool_t *pool = (threadpool_t *)threadpool;
while (!pool->shutdown)
{
sleep(DEFAULT_TIME); //定时 对线程池进行管理
pthread_mutex_lock(&(pool->lock));
int queue_size = pool->queue_size; //关注任务数
int live_thr_num = pool->live_thr_num; //存活线程的数量
pthread_mutex_unlock(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
int busy_thr_num = pool->busy_thr_num; /* 忙着的线程数 */
pthread_mutex_unlock(&(pool->thread_counter));
/* 创建新线程 算法: 任务数大于最小线程池个数, 且存活的线程数少于最大线程个数时 如:30>=10 && 40<100*/
if (queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num) {
pthread_mutex_lock(&(pool->lock));
int add = 0;
/*一次增加 DEFAULT_THREAD 个线程*/
for (int i = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_VARY
&& pool->live_thr_num < pool->max_thr_num; i++) {
if (pool->threads[i] == 0 || !is_thread_alive(pool->threads[i])) {
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
add++;
pool->live_thr_num++;
}
}
pthread_mutex_unlock(&(pool->lock));
}
/* 销毁多余的空闲线程 算法:忙线程X2 小于 存活的线程数 且 存活的线程数 大于 最小线程数时*/
if ((busy_thr_num * 2) < live_thr_num && live_thr_num > pool->min_thr_num) {
/* 一次销毁DEFAULT_THREAD个线程, 隨機10個即可 */
pthread_mutex_lock(&(pool->lock));
pool->wait_exit_thr_num = DEFAULT_THREAD_VARY; /* 要销毁的线程数 设置为10 */
pthread_mutex_unlock(&(pool->lock));
for (i = 0; i < DEFAULT_THREAD_VARY; i++) {
/* 通知处在空闲状态的线程, 他们会自行终止*/
pthread_cond_signal(&(pool->queue_not_empty));
}
}
}
return NULL;
}
int pthread_destory(threadpool_t *pool)
{
if (pool == nullptr)
return -1;
pool->shutdown = true;
//线销毁管理者线程
pthread_join(pool->adjust_tid, NULL);
for (int i = 0, i < pool->live_thr_num; i++)
{
//通知所有的空闲线程
pthread_cond_broadcast(&(pool->queue_not_empty));
}
for (int i = 0; i < pool->live_thr_num; i++)
{
pthread_join(pool->threads[i], NULL);
}
threadpool_free(pool);
return 0;
}
//清空动态内存
int threadpool_free(threadpool_t *pool)
{
if (pool == NULL) {
return -1;
}
if (pool->task_queue) {
free(pool->task_queue);
}
if (pool->threads) {
free(pool->threads);
pthread_mutex_lock(&(pool->lock));
pthread_mutex_destroy(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
pthread_mutex_destroy(&(pool->thread_counter));
pthread_cond_destroy(&(pool->queue_not_empty));
pthread_cond_destroy(&(pool->queue_not_full));
}
free(pool);
pool = NULL;
return 0;
}
int threadpool_all_threadnum(threadpool_t *pool)
{
int all_threadnum = -1;
pthread_mutex_lock(&(pool->lock));
all_threadnum = pool->live_thr_num;
pthread_mutex_unlock(&(pool->lock));
return all_threadnum;
}
int threadpool_busy_threadnum(threadpool_t *pool)
{
int busy_threadnum = -1;
pthread_mutex_lock(&(pool->thread_counter));
busy_threadnum = pool->busy_thr_num;
pthread_mutex_unlock(&(pool->thread_counter));
return busy_threadnum;
}
int is_thread_alive(pthread_t tid)
{
int kill_rc = pthread_kill(tid, 0); //发0号信号,测试线程是否存活
if (kill_rc == ESRCH) {
return false;
}
return true;
}
epoll
epoll使用边沿非阻塞模式,与epoll水平触发模式相比,其优势主要在可以避免同一个文件描述符触发epoll_wait,提高效率,值得注意的是边沿非阻塞模式中当缓冲区数据为空时,会产生读错误,即len = -1,此时应该判定errno是否等于EAGAIN,不管是阻塞模式还是非阻塞模式,len = 0。
#include <sys/epoll.h>
#include <errno.h>
#include "threadpool.h"
#include "epoll.h"
struct epoll_event *events;
int epoll_init()
{
int epoll_fd = epoll_create(LISTENQ + 1);
if (epoll_fd == -1)
return -1;
events = new epoll_event[MAXEVENTS];
return epoll_fd;
}
//注册新的描述符
int epoll_add(int epoll_fd, int fd, int32_t events)
{
struct epoll_events event;
event.data.fd = fd;
event.events = events;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0)
{
perror("epoll_add error");
return -1;
}
return 0;
}
//修改文件描述符
int epoll_mod(int epoll_fd, int fd, int32_t events)
{
struct epoll_event event;
event.data.fd = fd;
event.events = events;
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0)
{
printf("epoll_mod error");
return -1;
}
return 0;
}
//从epoll中删除描述符
int epoll_del(int epoll_fd, int fd, int32_t events)
{
struct epoll_event event;
event.data.fd = fd;
event.events = event;
if (epoll_del(epoll_fd, EPOLL_CTL_DEL, fd, &event) < 0)
{
printf("epoll_del error");
return -1;
}
return 0;
}
// 返回活跃事件数
int my_epoll_wait(int epoll_fd, struct epoll_event* events, int max_events, int timeout)
{
int ret_count = epoll_wait(epoll_fd, events, max_events, timeout);
if (ret_count < 0)
{
perror("epoll wait error");
}
return ret_count;
}
#ifndef _EPOLL_
#define _EPOLL_
const int MAXEVENTS = 5000;
const int LISTENQ = 1024;
int epoll_init();
int epoll_add(int epoll_fd, int fd, int32_t events);
int epoll_mod(int epoll_fd, int fd, int32_t events);
int epoll_del(int epoll_fd, int fd, int32_t events);
int my_epoll_wait(int epoll_fd, struct epoll_event *events, int max_events, int timeout);
#endif
#ifndef _UTIL_
#define _UTIL_
void handle_for_sigpipe();
int setSocketNonBlocking(int fd);
#endif //
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include "util.h"
void handle_for_sigpipe()
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
sa.sa_flag = 0;
if (sigaction(SIGPIPE, &sa, NULL))
return;
}
int setSocketNonBlocking(int fd)
{
int flag = fcntl(fd, F_GETFL, 0);
if (flag == -1)
return -1;
flag |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flag) == -1)
{
return -1;
}
return 0;
}
#include <sys/epoll.h>
#include <queue>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include <stdint.h>
#include "util.h"
#include "epoll.h"
#include "threadpool.h"
using namespace std;
const int THREADPOOL_THREAD_NUM = 3;
const int QUEUE_SIZE = 65535;
const int PORT = 6789;
//函数定义
int socket_bind_listen(int port);
void handle_events(int epoll_fd, int listen_fd, struct epoll_event *events, int events_num, threadpool_t* tp);
void acceptConnection(int listen_fd, int epoll_fd);
void myHandler(void *args);
int main()
{
//客服端发送消息后,然后关闭客服端,服务器返回消息时,就会收到内核给的SIGPIPE信号,服务器关闭,所以调用handle_for_sigpipe()处理这种情况
handle_for_sigpipe();
int epoll_fd = epoll_init();
if (epoll_fd < 0)
{
perror("epoll init failed");
return 1;
}
threadpool_t *threadpool = threadpool_create(THREADPOOL_THREAD_NUM, QUEUE_SIZE, QUEUE_SIZE);
//创建监听文件描述符
int listen_fd = socket_bind_listen(PORT);
if (listen_fd < 0)
{
perror("socket bind error");
return 1;
}
int32_t event = EPOLLIN | EPOLLET;
epoll_add(epoll_fd, fd, event);
while (1)
{
int event_num = my_poll_wait(epoll_fd, events, MAXEVENTS, -1);
if (event_num == 0)
continue;
printf("%d\n", event_num);
// 遍历events数组,根据监听种类及描述符类型分发操作
handle_events(epoll_fd, listen_fd, events, events_num,threadpool);
//handle_expired_event(); //清除空间
}
return 0;
}
int socket_bind_listen(int port)
{
//检查port的值,取正确空间,由于0~1024为系统使用的端口
if (port < 1024 && port > 65535)
return -1;
//创建socket(IPV4 +TCP),返回监听的文件描述符
int listen_fd = 0;
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return -1;
/* 消除bind时"Address already in use"错误,即端口复用。
虽然用Ctrl+C强制结束了进程,但错误依然存在,
用netstat -an |grep 5120和ps aux |grep 5120都还能看到刚才用Ctrl+C“强制结束”了的进程,
端口还是使用中,只好每次用kill结束进程,很是麻烦。这种情况是由于四次挥手时,TIME_WAIT能等待
2个报文存活长度的时间(30s左右,视机器而定),在 TIME_WAIT 状态退出之后,套接字被删除,
该地址才能被重新绑定而不出问题。*/
int optval = 0;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1)
return -1;
//设置服务器IP和port,和监听文件描述符
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htonl((unsigned short)port);
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
return -1;
//开始监听,最大等待队列长为LISTENQ;
if (listen(listen_fd, LISTENQ) == -1)
return -1;
//无效的监听描述符
if (listen_fd == -1)
{
close(listen_fd);
return -1;
}
return listen_fd;
}
void handle_events(int epoll_fd, int listen_fd, struct epoll_event *events, int events_num, threadpool_t* tp);
{
for (int i = 0; i < event_num; i++)
{
//获取文件描述符
int fd = events[i].data.fd;
//有事件发生的描述符为监听描述符
if (fd == listen_fd)
{
acceptConnection(listen_fd, epoll_fd);
}
else
{
//排除错误事件
if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i).events & EPOLLIN))
{
printf("error event\n");
continue;
}
//将请求的任务加入到线程中
int rc = threadpool_add(tp, myHander, NULL);
}
}
}
void acceptConnection(int listen_fd, int epoll_fd)
{
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_addr_len = 0;
int accept_fd = 0;
while ((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len) > 0))
{
//设置非阻塞模式
int ret = setSocketNonBlocking(accept_fd);
if (ret < 0)
{
perror("Set non block failed");
return;
}
// 文件描述符可以读,边缘触发(Edge Triggered)模式,保证一个socket连接在任一时刻只被一个线程处理
//EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT;
epoll_add(epoll_fd, accept_fd, _epo_event);
}
}
void myHandler(void *args)
{
char buff[MAX_BUFF];
while ((len = recv(fd, buf, sizeof(buf), 0)) > 0)
{
// 数据打印到终端
write(STDOUT_FILENO, buf, len);
// 发送给客户端
send(fd, buf, len, 0);
}
if (len == 0)
{
printf("客户端断开了连接\n");
//ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
if (ret == -1)
{
perror("epoll_ctl - del error");
exit(1);
}
//close(fd);
}
else if (len == -1)
{
if (errno == EAGAIN)
{
printf("缓冲区数据已经读完\n");
}
else
{
printf("recv error----\n");
exit(1);
}
}
if (len == -1)
{
perror("recv error");
exit(1);
}
else if (len == 0)
{
printf("client disconnected ....\n");
if (ret == -1)
{
perror("epoll_ctl - del error");
exit(1);
}
//close(fd);
}
else
{
// printf(" recv buf: %s\n", buf);
write(STDOUT_FILENO, buf, len);
write(fd, buf, len);
}
}
自己学习过程的记录,仅供参考