《linux高性能服務器編程》—— 一種高效的異步線程池的實現

此文與《linux高性能服務器編程》—— 一種高效的異步進程池的實現 是姊妹篇。

(1)線程池框架

(2)源代碼及註釋

#ifndef THREADPOOL  
#define THREADPOOL

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <set>

class thread //工作線程類
{
public:
    pthread_t tid; //線程標識
    int pipefd[2]; //與主線程通信的管道
};

template <typename T> //T是客戶類
class threadpool //線程池類
{
private:
    static const int MAX_THREAD_NUMBER = 16; //工作線程的最大數量
    static const int USER_PER_THREAD = 1000; //每個工作線程負責的客戶的最大數量
    static const int MAX_EVENT_NUMBER = 1000; //epoll內核事件表所能容納的事件的最大數量

    int thread_number;  //工作線程的實際數量
    int listenfd; //指向監聽socket的fd,主/工作線程共用同一個
    thread* work_thread; //管理各工作線程的信息
    static threadpool* instance; //指向此類的唯一對象
private:
    threadpool(int _listenfd, int _thread_number = 8);
    static void* worker(void* arg); //工作線程的執行函數
public:
    static threadpool* create(int listenfd, int thread_number = 8)
    //返回此線程池類的唯一對象
    {
        if(!instance)
            instance = new threadpool(listenfd, thread_number);
        return instance;
    }
    static threadpool* get_instance() 
    {
        return instance;
    }
    ~threadpool()
    {
        delete[] work_thread;
    }
    void run(); //執行主線程的任務
};
template <typename T>
threadpool<T>* threadpool<T>::instance = NULL;

int sig_pipefd[2]; //信號管道,主/工作線程都需要

int setnonblocking(int fd) //將fd設爲非阻塞
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void addfd(int epollfd, int fd, std::set<int> &used_fd)
//將fd的讀事件插入epoll內核事件表內,並打開此fd的ET模式,並用used_fd記錄此fd
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN |EPOLLET;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    used_fd.insert(fd);
    setnonblocking(fd);
}

void removefd(int epollfd, int fd, std::set<int> &used_fd)
//將fd的所有事件從epoll內核事件表內移除,並從used_fd中移去fd
{
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
    used_fd.erase(fd);
}

void close_fd(int epollfd, std::set<int> &used_fd)
//從epoll內核事件表內除去所有的fd及相關事件
{
    for(auto fd : used_fd)
    {
        epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
        close(fd);
    }
    used_fd.clear();
    close(epollfd);
}

void sig_handler(int sig)
//信號捕捉函數,即接收到此信號時,把信號從[1]端送入信號管道內,實現統一事件源
{
    int save_errno = errno;
    int msg = sig;
    send(sig_pipefd[1], (char*)&msg, 1, 0);
    errno = save_errno;
}

void addsig(int sig, void(*_handler)(int), bool restart = true)
//對信號設置信號捕捉函數
{
    struct sigaction sa;
    memset(&sa, '\0', sizeof(sa));
    sa.sa_handler = _handler;
    if(restart)
    {
        sa.sa_flags |= SA_RESTART;
    }
    sigfillset(&sa.sa_mask);
    assert(sigaction(sig, &sa, NULL) != -1);
}

template <typename T>
threadpool<T>::threadpool(int _listenfd, int _thread_number)
: thread_number(_thread_number), listenfd(_listenfd)
{
    assert((thread_number > 0) && (thread_number <= MAX_THREAD_NUMBER));

    work_thread = new thread[thread_number]; //管理各工作進程線程的信息的數組
    assert(work_thread);

    for(int i = 0;i < thread_number;++i)
    {
        //使主線程與各工作線程的通信管道變爲全雙工
        int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, work_thread[i].pipefd);
        assert(ret == 0);

        //將此線程的數據傳遞給其執行函數
        ret = pthread_create(&work_thread[i].tid, NULL, worker, work_thread+i);
        assert(ret == 0);

        //使主線程與工作線程分離,工作線程結束運行後其資源自動被回收
        ret = pthread_detach(work_thread[i].tid);
        assert(ret == 0);
    }
}


template <typename T>
void* threadpool<T>::worker(void *arg)
//工作線程的執行函數
{
    //此線程的數據
    thread* cur_thread = (thread*)arg;

    //用於保存被插入到epoll內核事件表內的fd
    std::set<int> used_fd;
    
    int epollfd = epoll_create(5);
    assert(epollfd != -1);

    //將和主進程通信的管道[0]端的讀事件插入epoll內核事件表內
    int pipefd = cur_thread->pipefd[0];
    addfd(epollfd, pipefd, used_fd);

    epoll_event events[MAX_EVENT_NUMBER];

    //保存客戶數據的數組
    T *users = new T[USER_PER_THREAD];
    assert(users);

    int ret;
    bool stop = false;
    
    while(!stop)
    {
        int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);

        if((number < 0) && (errno != EINTR))
        {
            printf("epoll failure\n");
            break;
        }

        for(int i = 0;i < number && !stop;++i)
        {
            int sockfd = events[i].data.fd;
            if((sockfd == pipefd) && (events[i].events & EPOLLIN))
            //來自主進程的數據,說明有客戶發起連接請求
            {
                int client = 0;

                ret = recv(sockfd, (char*)&client, sizeof(client), 0);
                if(ret <= 0)
                {
                    continue;
                }
                else
                //接受客戶連接請求
                {
                    struct sockaddr_in address;
                    socklen_t addrlength = sizeof(address);
                    int connfd = accept(get_instance()->listenfd, (struct sockaddr*)&address,
                                    &addrlength);
                    if(connfd < 0)
                    {
                        printf("error is %d\n", errno);
                        continue;
                    }
                    else if(connfd >= USER_PER_THREAD)
                    //此線程管理的用戶數量已達到上限,關閉此連接
                    {
                        const char *msg = "sorry!";
                        send(connfd, msg, sizeof(msg), 0);
                        close(connfd);
                        continue;
                    }
                    //將此連接socket的讀事件插入epoll內核事件表內
                    addfd(epollfd, connfd, used_fd);

                    //初始化客戶數據,以連接socket的fd值作爲客戶序號
                    users[connfd].init(epollfd, connfd, address);
                }
            }
            else if(events[i].events & EPOLLIN)
            //來自某連接socket的數據,說明客戶發起了某個請求
            {
                //調用客戶類的方法進行處理
                users[sockfd].process(used_fd);
            }
        }
    }
    delete[] users; //回收動態資源
    close_fd(epollfd, used_fd); //關閉所有fd
    cur_thread->tid = -1; //在線程信息數組中標記此線程已結束運行
    pthread_exit(NULL);
}

template <typename T>
void threadpool<T>::run()
//主線程運行的代碼
{
    //用於保存被插入到epoll內核事件表內的fd
    std::set<int> used_fd;

    int epollfd = epoll_create(5);
    assert(epollfd != -1);

    //將信號管道設爲全雙工模式
    int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sig_pipefd);
    assert(ret != -1);

    setnonblocking(sig_pipefd[1]);

    //將信號管道的讀事件插入epoll內核事件表內,實現統一事件源
    addfd(epollfd, sig_pipefd[0], used_fd);

    //捕捉以下信號
    addsig(SIGTERM, sig_handler);
    addsig(SIGINT, sig_handler);
    //忽略此信號
    addsig(SIGPIPE, SIG_IGN);

    //將監聽socket的讀事件插入epoll內核事件表內
    addfd(epollfd, listenfd, used_fd);
    
    epoll_event events[MAX_EVENT_NUMBER];

    //挑選工作線程的起點序號
    int sub_thread_counter = 0;
    int number = 0;
    bool stop = false;

    while(!stop)
    {
        number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);

        if((number < 0) && (errno != EINTR))
        {
            printf("epoll failure\n");
            break;
        }

        for(int i = 0;i < number && !stop;++i)
        {
            int sockfd = events[i].data.fd;

            if(sockfd == listenfd)
            //來自監聽socket的數據,說明有客戶發起連接請求
            {
                int j = sub_thread_counter;
                do
                //採用Round Robin方式挑選出一個工作線程
                {
                    if(work_thread[j].tid != -1)
                        break;
                    j = (j + 1) % thread_number;
                } while(j != sub_thread_counter);
                
                if(work_thread[j].tid == -1)
                //所有工作線程已結束運行則主線程也退出
                {
                    stop = true;
                    break;
                }

                sub_thread_counter = (j + 1) % thread_number;

                //向挑選出的工作線程通知:有客戶發起連接請求
                int msg = 1;
                send(work_thread[j].pipefd[1], (char*)&msg, sizeof(msg), 0);
            }
            else if((sockfd == sig_pipefd[0]) && (events[i].events & EPOLLIN))
            //來自信號管道的數據
            {
                char signals[1024];
                
                ret = recv(sig_pipefd[0], signals, sizeof(signals), 0);
                if(ret <= 0)
                    continue;
                else
                {
                    for(int j = 0;j < ret && !stop;++i)
                    {
                        switch(signals[j])
                        {
                            case SIGTERM:
                            case SIGINT:
                            //收到中止信號,結束前先殺死所有工作線程
                            {
                                for(int k = 0;k < thread_number;++k)
                                {
                                    unsigned long tid = work_thread[k].tid;
                                    if(tid != -1)
                                    {
                                        pthread_cancel(tid);
                                    }
                                }
                                stop = true;
                                break;
                            }
                        }
                    }
                }
                
            }
        }
    }
    //關閉所有fd
    close_fd(epollfd, used_fd);
    close(sig_pipefd[1]);
}

#endif

(3)用於測試線程池的簡單客戶類的實現

class client //客戶類
{
private:
    static const int BUFFER_SIZE = 1024; //緩衝區大小
    int epollfd; //負責此客戶的工作進程的epollfd
    sockaddr_in client_addr; //客戶socket地址
    char buf[BUFFER_SIZE]; //緩衝區
public:
    int connfd; //與此客戶通信的socket
    void init(int _epollfd, int _connfd, const sockaddr_in& _client_addr)
    //初始化客戶數據
    {
        epollfd = _epollfd;
        connfd = _connfd;
        client_addr = _client_addr;
        memset(buf, '\0', BUFFER_SIZE);
    }
    void process(std::set<int> &used_fd)
    //當客戶有數據發來時,進程的迴應:把客戶發來的數據原封不動地返回給客戶,僅用於測試
    {
        memset(buf, '\0', sizeof(buf));
        int ret = recv(connfd, buf, sizeof(buf), 0);
        if((ret < 0 && errno != EAGAIN) || ret == 0)
        //客戶關閉連接或發生讀錯誤,則關閉連接
        {
            //回收文件描述符
            removefd(epollfd, connfd, used_fd);
            close(connfd);
        }
        else if(ret > 0)
        {
            //將數據返還給客戶
            send(connfd, buf, strlen(buf), 0);
        }
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章