select IO服务端回显耗时测试
使用apachebench进行测试,ubuntu 安装sudo apt-get install apache2-utils ,ab -n 2000000 -c 1000 -s 10 -k http://192.168.88.129:11111/
1.不使用select机制,一个客户端创建一个线程。
代码:server.c
#include <stdio.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/time.h> #include <unistd.h> #include <sys/select.h> #include <strings.h> #include <string.h> #include <fcntl.h> #include <netdb.h> #include <pthread.h> #include "perthread_server.h" static int client_fds[MAX_SELECT_CLIENT_NUM] = {0}; static pthread_t c_pthreads[MAX_SELECT_CLIENT_NUM] = {0}; static reg_rcv_handle_call_back callbackfunc = NULL; static int server_create(int port); static void *select_loop(void *arg); static void *rcv_loop(void *arg); /***************************************** *func:server_init *describe:初始化服务器端 *para: port 端口号 *return:成功则返回0,error则返回RET_VAL_FAILED *****************************************/ int server_init(const int port, const reg_rcv_handle_call_back func) { int cnt = 10; static int ret_fd = -1; int ret; while (cnt--) { ret_fd = server_create(port); if (RET_VAL_FAILED != ret_fd) { if (NULL == callbackfunc) callbackfunc = func; break; } usleep(100 * 1000); } if (ret_fd < 0) { return RET_VAL_FAILED; } cnt = 10; while (cnt--) { pthread_t loop_thread_id; ret = pthread_create(&loop_thread_id, NULL, select_loop, &ret_fd); if (0 == ret) { pthread_detach(loop_thread_id); printf("init done ,wait client connect...\n"); return RET_VAL_SUCCESS; } } return RET_VAL_FAILED; } /***************************************** *func:server_create *describe:创建监听的fd *para: port 端口号 *return: 成功则返回监听的fd,error则返回RET_VAL_FAILED *****************************************/ int server_create(int port) { int server_fd = -1; struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf(" create error: %s(errno: %d)\n", strerror(errno), errno); return RET_VAL_FAILED; } //设置端口复用,防止被占用,服务器退出时再次绑定不会影响再次绑定 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(int)) < 0) { printf(" set server_fd option SO_REUSEADDR error !"); goto failed; } //绑定端口号 if (bind(server_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf(" bind error: %s(errno: %d)\n", strerror(errno), errno); goto failed; } //设置不阻塞模式 int flags, s; flags = fcntl(server_fd, F_GETFL, 0); if (flags == RET_VAL_FAILED) { perror("server_fd fcntl set error ,F_GETFL\n"); goto failed; } flags |= O_NONBLOCK; s = fcntl(server_fd, F_SETFL, flags); if (s == RET_VAL_FAILED) { perror("server_fd fcntl set error F_SETFL\n"); goto failed; } int retval = listen(server_fd, SOMAXCONN); if (retval == RET_VAL_FAILED) { perror("server_fd listen error\n"); goto failed; } return server_fd; failed: close(server_fd); return RET_VAL_FAILED; } void *select_loop(void *arg) { int listen_fd = *((int *)arg); int retval; while (1) { struct sockaddr_in client_address; socklen_t address_len; int client_sock_fd = accept(listen_fd, (struct sockaddr *)&client_address, &address_len); if (client_sock_fd > 0) { int fd_flags = RET_VAL_FAILED; //一个客户端到来分配一个fd,超过CLI_NUM以后跳出for循环,flags重新被赋值为RET_VAL_FAILED for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { if (client_fds[i] == 0) { fd_flags = i; client_fds[i] = client_sock_fd; break; } } if (fd_flags != RET_VAL_FAILED) { pthread_create(&(c_pthreads[fd_flags]), NULL, rcv_loop, &(client_fds[fd_flags])); pthread_detach(c_pthreads[fd_flags]); printf("new user client[%d]=%d add sucessfully!\n", fd_flags, client_sock_fd); } else //fd_flags=RET_VAL_FAILED { printf("the client nums is more than %d, can't login.close now!\n", MAX_SELECT_CLIENT_NUM); close(client_sock_fd); } } } } void get_all_client(int out_clients[], const int size) { if (size > MAX_SELECT_CLIENT_NUM) { for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { out_clients[i] = client_fds[i]; } } else { for (int i = 0; i < size; i++) { out_clients[i] = client_fds[i]; } } } static void *rcv_loop(void *arg) { int rcv_fd = *((int *)arg); char rcv_buff[MAX_RCV_BUFF_SIZE] = {0}; printf("init rcv_loop\n"); char max_cache_rub[1024*1024*6];//4M printf("init max_cache\n"); while (1) { int len = recv(rcv_fd, rcv_buff, MAX_RCV_BUFF_SIZE, 0); if (len > 0) { int send_len = send(rcv_fd, rcv_buff, len, 0); //printf("message form fd=%d,rcv len%d,send_len=%d\n", rcv_fd, len, send_len); if (NULL != callbackfunc) callbackfunc(rcv_fd, rcv_buff, len); } else if (len <= 0) { close(rcv_fd); for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { if (client_fds[i] == rcv_fd) { c_pthreads[i] = 0; client_fds[i] = 0; } } if (0 == len) { printf("fd=[%d] exit by itself!bye\n", rcv_fd); } else { printf("fd=[%d] is error!\n", rcv_fd); } pthread_exit(NULL); break; } } }
server.h
#ifndef _SELECTIO_SERVER_H_ #define _SELECTIO_SERVER_H_ #ifdef __cplusplus extern "C" { #endif #ifndef RET_VAL_SUCCESS #define RET_VAL_SUCCESS (0) #endif #ifndef RET_VAL_FAILED #define RET_VAL_FAILED (-1) #endif #ifndef MAX_SELECT_CLIENT_NUM #define MAX_SELECT_CLIENT_NUM (1020)//不要超过1021 #endif #ifndef MAX_RCV_BUFF_SIZE #define MAX_RCV_BUFF_SIZE (4096) #endif typedef struct _client_info_t { int fd; int login_id; }client_info_t; typedef int (*reg_rcv_handle_call_back)(const int fd,const void *data,const int data_len); /***************************************** *func:server_init *describe:初始化服务器端 *para: port 端口号 *return:成功则返回0,error则返回-1 *****************************************/ int server_init(const int port,const reg_rcv_handle_call_back func); void get_all_client(int out_clients[],const int size); #ifdef __cplusplus } #endif #endif
main.c
lude <stdio.h> #include <stdio.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/time.h> #include <unistd.h> #include <sys/select.h> #include <strings.h> #include <string.h> #include <fcntl.h> #include <netdb.h> #include <pthread.h> #include "perthread_server.h" int handle_call_back(const int fd,const void *data,const int data_len) { //printf("fd=%d,len=%d,data:%s\n",fd,data_len,(char *)data); } int main(int argc, char **argv) { server_init(11111, handle_call_back); while (1) { sleep(5); } }
使用ab工具测试结果:ab -n 2000000 -c 1000 -k http://192.168.1.1:11111/
2.使用select机制,一个线程处理accept和recv
server.c
#include <stdio.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/time.h> #include <unistd.h> #include <sys/select.h> #include <strings.h> #include <string.h> #include <fcntl.h> #include <netdb.h> #include <pthread.h> #include "selectio_server.h" static int client_fds[MAX_SELECT_CLIENT_NUM] = {0}; static reg_rcv_handle_call_back callbackfunc = NULL; static int server_create(int port); static void *select_loop(void *arg); /***************************************** *func:server_init *describe:初始化服务器端 *para: port 端口号 *return:成功则返回0,error则返回RET_VAL_FAILED *****************************************/ int server_init(const int port, const reg_rcv_handle_call_back func) { int cnt = 10; static int ret_fd = -1; int ret; while (cnt--) { ret_fd = server_create(port); if (RET_VAL_FAILED != ret_fd) { if (NULL == callbackfunc) callbackfunc = func; break; } usleep(100 * 1000); } if (ret_fd < 0) { return RET_VAL_FAILED; } cnt = 10; while (cnt--) { pthread_t loop_thread_id; ret = pthread_create(&loop_thread_id, NULL, select_loop, &ret_fd); if (0 == ret) { pthread_detach(loop_thread_id); printf("init done ,wait client connect...\n"); return RET_VAL_SUCCESS; } } return RET_VAL_FAILED; } /***************************************** *func:server_create *describe:创建监听的fd *para: port 端口号 *return: 成功则返回监听的fd,error则返回RET_VAL_FAILED *****************************************/ int server_create(int port) { int server_fd = -1; struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf(" create error: %s(errno: %d)\n", strerror(errno), errno); return RET_VAL_FAILED; } //设置端口复用,防止被占用,服务器退出时再次绑定不会影响再次绑定 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(int)) < 0) { printf(" set server_fd option SO_REUSEADDR error !"); goto failed; } //绑定端口号 if (bind(server_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf(" bind error: %s(errno: %d)\n", strerror(errno), errno); goto failed; } //设置不阻塞模式 int flags, s; flags = fcntl(server_fd, F_GETFL, 0); if (flags == RET_VAL_FAILED) { perror("server_fd fcntl set error ,F_GETFL\n"); goto failed; } flags |= O_NONBLOCK; s = fcntl(server_fd, F_SETFL, flags); if (s == RET_VAL_FAILED) { perror("server_fd fcntl set error F_SETFL\n"); goto failed; } int retval = listen(server_fd, SOMAXCONN); if (retval == RET_VAL_FAILED) { perror("server_fd listen error\n"); goto failed; } return server_fd; failed: close(server_fd); return RET_VAL_FAILED; } void *select_loop(void *arg) { int listen_fd = *((int *)arg); int max_fd = 0; int retval; fd_set ser_fdset; struct timeval wait_timeout; //select多路复用 while (1) { FD_ZERO(&ser_fdset); max_fd = listen_fd; for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { if (client_fds[i] > 0) { FD_SET(client_fds[i], &ser_fdset); if (max_fd <= client_fds[i]) { max_fd = client_fds[i]; } } } FD_SET(listen_fd, &ser_fdset); wait_timeout.tv_sec = 1; //1s wait_timeout.tv_usec = 0; retval = select(max_fd + 1, &ser_fdset, NULL, NULL, &wait_timeout); if (retval > 0) { if (FD_ISSET(listen_fd, &ser_fdset)) //有新的客户端连接 { struct sockaddr_in client_address; socklen_t address_len; int client_sock_fd = accept(listen_fd, (struct sockaddr *)&client_address, &address_len); if (client_sock_fd > 0) { int flags = RET_VAL_FAILED; //一个客户端到来分配一个fd,超过CLI_NUM以后跳出for循环,flags重新被赋值为RET_VAL_FAILED for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { if (client_fds[i] == 0) { flags = i; client_fds[i] = client_sock_fd; break; } } if (flags != RET_VAL_FAILED) { printf("new user client[%d]=%d add sucessfully!\n", flags, client_sock_fd); } else //flags=RET_VAL_FAILED { printf("the client nums is more than %d, can't login.close now!\n", MAX_SELECT_CLIENT_NUM); close(client_sock_fd); } } } else { //deal with the message for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { //if (client_fds[i] > 0) { if (FD_ISSET(client_fds[i], &ser_fdset)) { char rcv_buff[MAX_RCV_BUFF_SIZE] = {0}; int len = recv(client_fds[i], rcv_buff, MAX_RCV_BUFF_SIZE, MSG_DONTWAIT); if (len > 0) { int send_len = send(client_fds[i], rcv_buff, len, MSG_DONTWAIT); //printf("message form client[%d]=%d,rcv len%d,send_len=%d\n", i, client_fds[i], len, send_len); if (NULL != callbackfunc) callbackfunc(client_fds[i], rcv_buff, len); continue; } else if (len <= 0) { close(client_fds[i]); client_fds[i] = 0; if (0 == len) { printf("clien[%d] exit by itself!bye\n", i); } else { printf("clien[%d] is error!\n", i); } } } } } } } if (retval < 0) { printf("select error:%s\n", strerror(errno)); } else if (0 == retval) { //printf("select get timeout!\n"); continue; } } } void get_all_client(int out_clients[], const int size) { if (size > MAX_SELECT_CLIENT_NUM) { for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { out_clients[i] = client_fds[i]; } } else { for (int i = 0; i < size; i++) { out_clients[i] = client_fds[i]; } } }
测试结果:
3.使用select机制,select、accept、rec都交给线程池进行
server.c
#include <stdio.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/time.h> #include <unistd.h> #include <sys/select.h> #include <strings.h> #include <string.h> #include <fcntl.h> #include <netdb.h> #include <pthread.h> #include "thpool_server.h" #include "thpool.h" static int client_fds[MAX_SELECT_CLIENT_NUM] = {0}; static reg_rcv_handle_call_back callbackfunc = NULL; static threadpool thpool=NULL ; static int server_create(int port); static void *accept_loop(void *arg); void *select_loop(void *arg); static void *rcv_loop(void *arg); /***************************************** *func:server_init *describe:初始化服务器端 *para: port 端口号 *return:成功则返回0,error则返回RET_VAL_FAILED *****************************************/ int server_init(const int port, const reg_rcv_handle_call_back func) { int cnt = 10; static int ret_fd = -1; int ret; while (cnt--) { thpool= thpool_init(32); if (NULL!= thpool) { break; } usleep(100 * 1000); } if(NULL==thpool) { printf("thpool is NULL,error\n"); return RET_VAL_FAILED; } cnt = 10; while (cnt--) { ret_fd = server_create(port); if (RET_VAL_FAILED != ret_fd) { if(NULL == callbackfunc) callbackfunc = func; break; } usleep(100 * 1000); } if(ret_fd>0) { if(RET_VAL_SUCCESS!=thpool_add_work(thpool, (void*)accept_loop, (void*)(&ret_fd))) { printf("create accept_loop task error\n"); return RET_VAL_FAILED; } if(RET_VAL_SUCCESS!=thpool_add_work(thpool, (void*)select_loop, NULL)) { printf("create select_loop task error\n"); return RET_VAL_FAILED; } } return RET_VAL_SUCCESS; } /***************************************** *func:server_create *describe:创建监听的fd *para: port 端口号 *return: 成功则返回监听的fd,error则返回RET_VAL_FAILED *****************************************/ int server_create(int port) { int server_fd = -1; struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf(" create error: %s(errno: %d)\n", strerror(errno), errno); return RET_VAL_FAILED; } //设置端口复用,防止被占用,服务器退出时再次绑定不会影响再次绑定 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(int)) < 0) { printf(" set server_fd option SO_REUSEADDR error !"); goto failed; } //绑定端口号 if (bind(server_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf(" bind error: %s(errno: %d)\n", strerror(errno), errno); goto failed; } //设置不阻塞模式 int flags, s; flags = fcntl(server_fd, F_GETFL, 0); if (flags == RET_VAL_FAILED) { perror("server_fd fcntl set error ,F_GETFL\n"); goto failed; } flags |= O_NONBLOCK; s = fcntl(server_fd, F_SETFL, flags); if (s == RET_VAL_FAILED) { perror("server_fd fcntl set error F_SETFL\n"); goto failed; } int retval = listen(server_fd, SOMAXCONN); if (retval == RET_VAL_FAILED) { perror("server_fd listen error\n"); goto failed; } return server_fd; failed: close(server_fd); return RET_VAL_FAILED; } void *accept_loop(void *arg) { int listen_fd = *((int *)arg); int retval; while (1) { struct sockaddr_in client_address; socklen_t address_len; int client_sock_fd = accept(listen_fd, (struct sockaddr *)&client_address, &address_len); if (client_sock_fd > 0) { int fd_flags = RET_VAL_FAILED; //一个客户端到来分配一个fd,超过CLI_NUM以后跳出for循环,flags重新被赋值为RET_VAL_FAILED for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { if (client_fds[i] == 0) { fd_flags = i; client_fds[i] = client_sock_fd; break; } } if ( RET_VAL_FAILED==fd_flags) { printf("the client nums is more than %d, can't login.close now!\n", MAX_SELECT_CLIENT_NUM); close(client_sock_fd); } } } } void get_all_client(int out_clients[], const int size) { if (size > MAX_SELECT_CLIENT_NUM) { for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { out_clients[i] = client_fds[i]; } } else { for (int i = 0; i < size; i++) { out_clients[i] = client_fds[i]; } } } static void *rcv_loop(void *arg) { int rcv_fd = *((int *)arg); char rcv_buff[MAX_RCV_BUFF_SIZE] = {0}; int len=-1; do { len = recv(rcv_fd, rcv_buff, MAX_RCV_BUFF_SIZE, MSG_DONTWAIT);//非阻塞读 if (len > 0) { int send_len = send(rcv_fd, rcv_buff, len, 0); //printf("message form fd=%d,rcv len%d,send_len=%d\n", rcv_fd, len, send_len); if (NULL != callbackfunc) callbackfunc(rcv_fd, rcv_buff, len); } else if (len <= 0) { if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) { continue; } close(rcv_fd); for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { if (client_fds[i] == rcv_fd) { client_fds[i] = 0; } } if (0 == len) { printf("fd=[%d] exit by itself!bye\n", rcv_fd); } else { printf("fd=[%d] is error!\n", rcv_fd); } break; } } while (MAX_RCV_BUFF_SIZE==len); //printf("cliend %d task done \n",rcv_fd); } void *select_loop(void *arg) { int max_fd = 0; int retval; fd_set ser_fdset; struct timeval wait_timeout; //select多路复用 while (1) { FD_ZERO(&ser_fdset); max_fd = 0; for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { if (client_fds[i] > 0) { FD_SET(client_fds[i], &ser_fdset); if (max_fd <= client_fds[i]) { max_fd = client_fds[i]; } } } wait_timeout.tv_sec = 1; //1s wait_timeout.tv_usec = 0; retval = select(max_fd + 1, &ser_fdset, NULL, NULL, &wait_timeout); if (retval > 0) { //deal with the message for (int i = 0; i < MAX_SELECT_CLIENT_NUM; i++) { if (FD_ISSET(client_fds[i], &ser_fdset)) { if(RET_VAL_SUCCESS!=thpool_add_work(thpool, (void*)rcv_loop, (void*)(&(client_fds[i])))) { printf("create accept_loop task error\n"); continue; } } } } if (retval < 0) { printf("select error:%s\n", strerror(errno)); } else if (0 == retval) { //printf("select get timeout!\n"); continue; } } }
thread_pool.c (https://github.com/Pithikos/C-Thread-Pool)
/* ******************************** * Author: Johan Hanssen Seferidis * License: MIT * Description: Library providing a threading pool where you can add * work. For usage, check the thpool.h file or README.md * *//** @file thpool.h *//* * ********************************/ #define _POSIX_C_SOURCE 200809L #include <unistd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <errno.h> #include <time.h> #if defined(__linux__) #include <sys/prctl.h> #endif #include "thpool.h" #ifdef THPOOL_DEBUG #define THPOOL_DEBUG 1 #else #define THPOOL_DEBUG 0 #endif #if !defined(DISABLE_PRINT) || defined(THPOOL_DEBUG) #define err(str) fprintf(stderr, str) #else #define err(str) #endif static volatile int threads_keepalive; static volatile int threads_on_hold; /* ========================== STRUCTURES ============================ */ /* Binary semaphore */ typedef struct bsem { pthread_mutex_t mutex; pthread_cond_t cond; int v; } bsem; /* Job */ typedef struct job{ struct job* prev; /* pointer to previous job */ void (*function)(void* arg); /* function pointer */ void* arg; /* function's argument */ } job; /* Job queue */ typedef struct jobqueue{ pthread_mutex_t rwmutex; /* used for queue r/w access */ job *front; /* pointer to front of queue */ job *rear; /* pointer to rear of queue */ bsem *has_jobs; /* flag as binary semaphore */ int len; /* number of jobs in queue */ } jobqueue; /* Thread */ typedef struct thread{ int id; /* friendly id */ pthread_t pthread; /* pointer to actual thread */ struct thpool_* thpool_p; /* access to thpool */ } thread; /* Threadpool */ typedef struct thpool_{ thread** threads; /* pointer to threads */ volatile int num_threads_alive; /* threads currently alive */ volatile int num_threads_working; /* threads currently working */ pthread_mutex_t thcount_lock; /* used for thread count etc */ pthread_cond_t threads_all_idle; /* signal to thpool_wait */ jobqueue jobqueue; /* job queue */ } thpool_; /* ========================== PROTOTYPES ============================ */ static int thread_init(thpool_* thpool_p, struct thread** thread_p, int id); static void* thread_do(struct thread* thread_p); static void thread_hold(int sig_id); static void thread_destroy(struct thread* thread_p); static int jobqueue_init(jobqueue* jobqueue_p); static void jobqueue_clear(jobqueue* jobqueue_p); static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob_p); static struct job* jobqueue_pull(jobqueue* jobqueue_p); static void jobqueue_destroy(jobqueue* jobqueue_p); static void bsem_init(struct bsem *bsem_p, int value); static void bsem_reset(struct bsem *bsem_p); static void bsem_post(struct bsem *bsem_p); static void bsem_post_all(struct bsem *bsem_p); static void bsem_wait(struct bsem *bsem_p); /* ========================== THREADPOOL ============================ */ /* Initialise thread pool */ struct thpool_* thpool_init(int num_threads){ threads_on_hold = 0; threads_keepalive = 1; if (num_threads < 0){ num_threads = 0; } /* Make new thread pool */ thpool_* thpool_p; thpool_p = (struct thpool_*)malloc(sizeof(struct thpool_)); if (thpool_p == NULL){ err("thpool_init(): Could not allocate memory for thread pool\n"); return NULL; } thpool_p->num_threads_alive = 0; thpool_p->num_threads_working = 0; /* Initialise the job queue */ if (jobqueue_init(&thpool_p->jobqueue) == -1){ err("thpool_init(): Could not allocate memory for job queue\n"); free(thpool_p); return NULL; } /* Make threads in pool */ thpool_p->threads = (struct thread**)malloc(num_threads * sizeof(struct thread *)); if (thpool_p->threads == NULL){ err("thpool_init(): Could not allocate memory for threads\n"); jobqueue_destroy(&thpool_p->jobqueue); free(thpool_p); return NULL; } pthread_mutex_init(&(thpool_p->thcount_lock), NULL); pthread_cond_init(&thpool_p->threads_all_idle, NULL); /* Thread init */ int n; for (n=0; n<num_threads; n++){ thread_init(thpool_p, &thpool_p->threads[n], n); #if THPOOL_DEBUG printf("THPOOL_DEBUG: Created thread %d in pool \n", n); #endif } /* Wait for threads to initialize */ while (thpool_p->num_threads_alive != num_threads) {} return thpool_p; } /* Add work to the thread pool */ int thpool_add_work(thpool_* thpool_p, void (*function_p)(void*), void* arg_p){ job* newjob; newjob=(struct job*)malloc(sizeof(struct job)); if (newjob==NULL){ err("thpool_add_work(): Could not allocate memory for new job\n"); return -1; } /* add function and argument */ newjob->function=function_p; newjob->arg=arg_p; /* add job to queue */ jobqueue_push(&thpool_p->jobqueue, newjob); return 0; } /* Wait until all jobs have finished */ void thpool_wait(thpool_* thpool_p){ pthread_mutex_lock(&thpool_p->thcount_lock); while (thpool_p->jobqueue.len || thpool_p->num_threads_working) { pthread_cond_wait(&thpool_p->threads_all_idle, &thpool_p->thcount_lock); } pthread_mutex_unlock(&thpool_p->thcount_lock); } /* Destroy the threadpool */ void thpool_destroy(thpool_* thpool_p){ /* No need to destory if it's NULL */ if (thpool_p == NULL) return ; volatile int threads_total = thpool_p->num_threads_alive; /* End each thread 's infinite loop */ threads_keepalive = 0; /* Give one second to kill idle threads */ double TIMEOUT = 1.0; time_t start, end; double tpassed = 0.0; time (&start); while (tpassed < TIMEOUT && thpool_p->num_threads_alive){ bsem_post_all(thpool_p->jobqueue.has_jobs); time (&end); tpassed = difftime(end,start); } /* Poll remaining threads */ while (thpool_p->num_threads_alive){ bsem_post_all(thpool_p->jobqueue.has_jobs); sleep(1); } /* Job queue cleanup */ jobqueue_destroy(&thpool_p->jobqueue); /* Deallocs */ int n; for (n=0; n < threads_total; n++){ thread_destroy(thpool_p->threads[n]); } free(thpool_p->threads); free(thpool_p); } /* Pause all threads in threadpool */ void thpool_pause(thpool_* thpool_p) { int n; for (n=0; n < thpool_p->num_threads_alive; n++){ pthread_kill(thpool_p->threads[n]->pthread, SIGUSR1); } } /* Resume all threads in threadpool */ void thpool_resume(thpool_* thpool_p) { // resuming a single threadpool hasn't been // implemented yet, meanwhile this supresses // the warnings (void)thpool_p; threads_on_hold = 0; } int thpool_num_threads_working(thpool_* thpool_p){ return thpool_p->num_threads_working; } /* ============================ THREAD ============================== */ /* Initialize a thread in the thread pool * * @param thread address to the pointer of the thread to be created * @param id id to be given to the thread * @return 0 on success, -1 otherwise. */ static int thread_init (thpool_* thpool_p, struct thread** thread_p, int id){ *thread_p = (struct thread*)malloc(sizeof(struct thread)); if (*thread_p == NULL){ err("thread_init(): Could not allocate memory for thread\n"); return -1; } (*thread_p)->thpool_p = thpool_p; (*thread_p)->id = id; pthread_create(&(*thread_p)->pthread, NULL, (void * (*)(void *)) thread_do, (*thread_p)); pthread_detach((*thread_p)->pthread); return 0; } /* Sets the calling thread on hold */ static void thread_hold(int sig_id) { (void)sig_id; threads_on_hold = 1; while (threads_on_hold){ sleep(1); } } /* What each thread is doing * * In principle this is an endless loop. The only time this loop gets interuppted is once * thpool_destroy() is invoked or the program exits. * * @param thread thread that will run this function * @return nothing */ static void* thread_do(struct thread* thread_p){ /* Set thread name for profiling and debuging */ char thread_name[32] = {0}; snprintf(thread_name, 32, "thread-pool-%d", thread_p->id); #if defined(__linux__) /* Use prctl instead to prevent using _GNU_SOURCE flag and implicit declaration */ prctl(PR_SET_NAME, thread_name); #elif defined(__APPLE__) && defined(__MACH__) pthread_setname_np(thread_name); #else err("thread_do(): pthread_setname_np is not supported on this system"); #endif /* Assure all threads have been created before starting serving */ thpool_* thpool_p = thread_p->thpool_p; /* Register signal handler */ struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = thread_hold; if (sigaction(SIGUSR1, &act, NULL) == -1) { err("thread_do(): cannot handle SIGUSR1"); } /* Mark thread as alive (initialized) */ pthread_mutex_lock(&thpool_p->thcount_lock); thpool_p->num_threads_alive += 1; pthread_mutex_unlock(&thpool_p->thcount_lock); while(threads_keepalive){ bsem_wait(thpool_p->jobqueue.has_jobs); if (threads_keepalive){ pthread_mutex_lock(&thpool_p->thcount_lock); thpool_p->num_threads_working++; pthread_mutex_unlock(&thpool_p->thcount_lock); /* Read job from queue and execute it */ void (*func_buff)(void*); void* arg_buff; job* job_p = jobqueue_pull(&thpool_p->jobqueue); if (job_p) { func_buff = job_p->function; arg_buff = job_p->arg; func_buff(arg_buff); free(job_p); } pthread_mutex_lock(&thpool_p->thcount_lock); thpool_p->num_threads_working--; if (!thpool_p->num_threads_working) { pthread_cond_signal(&thpool_p->threads_all_idle); } pthread_mutex_unlock(&thpool_p->thcount_lock); } } pthread_mutex_lock(&thpool_p->thcount_lock); thpool_p->num_threads_alive --; pthread_mutex_unlock(&thpool_p->thcount_lock); return NULL; } /* Frees a thread */ static void thread_destroy (thread* thread_p){ free(thread_p); } /* ============================ JOB QUEUE =========================== */ /* Initialize queue */ static int jobqueue_init(jobqueue* jobqueue_p){ jobqueue_p->len = 0; jobqueue_p->front = NULL; jobqueue_p->rear = NULL; jobqueue_p->has_jobs = (struct bsem*)malloc(sizeof(struct bsem)); if (jobqueue_p->has_jobs == NULL){ return -1; } pthread_mutex_init(&(jobqueue_p->rwmutex), NULL); bsem_init(jobqueue_p->has_jobs, 0); return 0; } /* Clear the queue */ static void jobqueue_clear(jobqueue* jobqueue_p){ while(jobqueue_p->len){ free(jobqueue_pull(jobqueue_p)); } jobqueue_p->front = NULL; jobqueue_p->rear = NULL; bsem_reset(jobqueue_p->has_jobs); jobqueue_p->len = 0; } /* Add (allocated) job to queue */ static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob){ pthread_mutex_lock(&jobqueue_p->rwmutex); newjob->prev = NULL; switch(jobqueue_p->len){ case 0: /* if no jobs in queue */ jobqueue_p->front = newjob; jobqueue_p->rear = newjob; break; default: /* if jobs in queue */ jobqueue_p->rear->prev = newjob; jobqueue_p->rear = newjob; } jobqueue_p->len++; bsem_post(jobqueue_p->has_jobs); pthread_mutex_unlock(&jobqueue_p->rwmutex); } /* Get first job from queue(removes it from queue) * Notice: Caller MUST hold a mutex */ static struct job* jobqueue_pull(jobqueue* jobqueue_p){ pthread_mutex_lock(&jobqueue_p->rwmutex); job* job_p = jobqueue_p->front; switch(jobqueue_p->len){ case 0: /* if no jobs in queue */ break; case 1: /* if one job in queue */ jobqueue_p->front = NULL; jobqueue_p->rear = NULL; jobqueue_p->len = 0; break; default: /* if >1 jobs in queue */ jobqueue_p->front = job_p->prev; jobqueue_p->len--; /* more than one job in queue -> post it */ bsem_post(jobqueue_p->has_jobs); } pthread_mutex_unlock(&jobqueue_p->rwmutex); return job_p; } /* Free all queue resources back to the system */ static void jobqueue_destroy(jobqueue* jobqueue_p){ jobqueue_clear(jobqueue_p); free(jobqueue_p->has_jobs); } /* ======================== SYNCHRONISATION ========================= */ /* Init semaphore to 1 or 0 */ static void bsem_init(bsem *bsem_p, int value) { if (value < 0 || value > 1) { err("bsem_init(): Binary semaphore can take only values 1 or 0"); exit(1); } pthread_mutex_init(&(bsem_p->mutex), NULL); pthread_cond_init(&(bsem_p->cond), NULL); bsem_p->v = value; } /* Reset semaphore to 0 */ static void bsem_reset(bsem *bsem_p) { bsem_init(bsem_p, 0); } /* Post to at least one thread */ static void bsem_post(bsem *bsem_p) { pthread_mutex_lock(&bsem_p->mutex); bsem_p->v = 1; pthread_cond_signal(&bsem_p->cond); pthread_mutex_unlock(&bsem_p->mutex); } /* Post to all threads */ static void bsem_post_all(bsem *bsem_p) { pthread_mutex_lock(&bsem_p->mutex); bsem_p->v = 1; pthread_cond_broadcast(&bsem_p->cond); pthread_mutex_unlock(&bsem_p->mutex); } /* Wait on semaphore until semaphore has value 0 */ static void bsem_wait(bsem* bsem_p) { pthread_mutex_lock(&bsem_p->mutex); while (bsem_p->v != 1) { pthread_cond_wait(&bsem_p->cond, &bsem_p->mutex); } bsem_p->v = 0; pthread_mutex_unlock(&bsem_p->mutex); }
thread_pool.h
/********************************** * @author Johan Hanssen Seferidis * License: MIT * **********************************/ #ifndef _THPOOL_ #define _THPOOL_ #ifdef __cplusplus extern "C" { #endif /* =================================== API ======================================= */ typedef struct thpool_* threadpool; /** * @brief Initialize threadpool * * Initializes a threadpool. This function will not return until all * threads have initialized successfully. * * @example * * .. * threadpool thpool; //First we declare a threadpool * thpool = thpool_init(4); //then we initialize it to 4 threads * .. * * @param num_threads number of threads to be created in the threadpool * @return threadpool created threadpool on success, * NULL on error */ threadpool thpool_init(int num_threads); /** * @brief Add work to the job queue * * Takes an action and its argument and adds it to the threadpool's job queue. * If you want to add to work a function with more than one arguments then * a way to implement this is by passing a pointer to a structure. * * NOTICE: You have to cast both the function and argument to not get warnings. * * @example * * void print_num(int num){ * printf("%d\n", num); * } * * int main() { * .. * int a = 10; * thpool_add_work(thpool, (void*)print_num, (void*)a); * .. * } * * @param threadpool threadpool to which the work will be added * @param function_p pointer to function to add as work * @param arg_p pointer to an argument * @return 0 on success, -1 otherwise. */ int thpool_add_work(threadpool, void (*function_p)(void*), void* arg_p); /** * @brief Wait for all queued jobs to finish * * Will wait for all jobs - both queued and currently running to finish. * Once the queue is empty and all work has completed, the calling thread * (probably the main program) will continue. * * Smart polling is used in wait. The polling is initially 0 - meaning that * there is virtually no polling at all. If after 1 seconds the threads * haven't finished, the polling interval starts growing exponentially * until it reaches max_secs seconds. Then it jumps down to a maximum polling * interval assuming that heavy processing is being used in the threadpool. * * @example * * .. * threadpool thpool = thpool_init(4); * .. * // Add a bunch of work * .. * thpool_wait(thpool); * puts("All added work has finished"); * .. * * @param threadpool the threadpool to wait for * @return nothing */ void thpool_wait(threadpool); /** * @brief Pauses all threads immediately * * The threads will be paused no matter if they are idle or working. * The threads return to their previous states once thpool_resume * is called. * * While the thread is being paused, new work can be added. * * @example * * threadpool thpool = thpool_init(4); * thpool_pause(thpool); * .. * // Add a bunch of work * .. * thpool_resume(thpool); // Let the threads start their magic * * @param threadpool the threadpool where the threads should be paused * @return nothing */ void thpool_pause(threadpool); /** * @brief Unpauses all threads if they are paused * * @example * .. * thpool_pause(thpool); * sleep(10); // Delay execution 10 seconds * thpool_resume(thpool); * .. * * @param threadpool the threadpool where the threads should be unpaused * @return nothing */ void thpool_resume(threadpool); /** * @brief Destroy the threadpool * * This will wait for the currently active threads to finish and then 'kill' * the whole threadpool to free up memory. * * @example * int main() { * threadpool thpool1 = thpool_init(2); * threadpool thpool2 = thpool_init(2); * .. * thpool_destroy(thpool1); * .. * return 0; * } * * @param threadpool the threadpool to destroy * @return nothing */ void thpool_destroy(threadpool); /** * @brief Show currently working threads * * Working threads are the threads that are performing work (not idle). * * @example * int main() { * threadpool thpool1 = thpool_init(2); * threadpool thpool2 = thpool_init(2); * .. * printf("Working threads: %d\n", thpool_num_threads_working(thpool1)); * .. * return 0; * } * * @param threadpool the threadpool of interest * @return integer number of threads working */ int thpool_num_threads_working(threadpool); #ifdef __cplusplus } #endif #endif
测试结果: