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

测试结果:

 

 结论:对于传输数据量小的,使用单一线程处理更适合。对于传输量大,且客户端数量多的,使用线程池处理更适合。对于客户端数量少,可以不使用select机制,一个客户端对用一条线程。(针对嵌入式linux系统测试)

 

posted @ 2021-04-24 15:14  jest549  阅读(87)  评论(0编辑  收藏  举报