1. 程式人生 > >Linux高效能網路:協程系列02-協程的起源

Linux高效能網路:協程系列02-協程的起源

目錄

2.協程的起源

  問題:協程存在的原因?協程能夠解決哪些問題?

  在我們現在CS,BS開發模式下,伺服器的吞吐量是一個很重要的引數。其實吞吐量是IO處理時間加上業務處理。為了簡單起見,比如,客戶端與伺服器之間是長連線的,客戶端定期給伺服器傳送心跳包資料。客戶端傳送一次心跳包到伺服器,伺服器更新該新客戶端狀態的。心跳包傳送的過程,業務處理時長等於IO讀取(RECV系統呼叫)加上業務處理(更新客戶狀態)。吞吐量等於1s業務處理次數。
處理流程
  業務處理(更新客戶端狀態)時間,業務不一樣的,處理時間不一樣,我們就不做討論。

  那如何提升recv的效能。若只有一個客戶端,recv的效能也沒有必要提升,也不能提升。若在有百萬計的客戶端長連線的情況,我們該如何提升。以Linux為例,在這裡需要介紹一個“網紅”就是epoll。伺服器使用epoll管理百萬計的客戶端長連線,程式碼框架如下:

while (1) {
    int nready = epoll_wait(epfd, events, EVENT_SIZE, -1);

    for (i = 0;i < nready;i ++) {

        int sockfd = events[i].data.fd;
        if (sockfd == listenfd) {
            int connfd = accept(listenfd, xxx, xxxx);

            setnonblock(connfd);

            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = connfd;
            epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

        } else {
            handle(sockfd);
        }
    }
}

  對於響應式伺服器,所有的客戶端的操作驅動都是來源於這個大迴圈。來源於epoll_wait的反饋結果。
對於伺服器處理百萬計的IO。Handle(sockfd)實現方式有兩種。

  • handle(sockfd)函式內部對sockfd進行讀寫動作
int handle(int sockfd) {

    recv(sockfd, rbuffer, length, 0);

    parser_proto(rbuffer, length);

    send(sockfd, sbuffer, length, 0);

}
  • handle的io操作(send,recv)與epoll_wait是在同一個處理流程裡面的。這就是IO同步操作。
    優點:
    1.sockfd管理方便。
    2.操作邏輯清晰。
    缺點:
    1.伺服器程式依賴epoll_wait的迴圈響應速度慢。
    2.程式效能差

  • handle(sockfd)函式內部將sockfd的操作,push到執行緒池中:
int thread_cb(int sockfd) {
    // 此函式是線上程池建立的執行緒中執行。
    // 與handle不在一個執行緒上下文中執行
    recv(sockfd, rbuffer, length, 0);
    parser_proto(rbuffer, length);
    send(sockfd, sbuffer, length, 0);
}

int handle(int sockfd) {
    //此函式在主執行緒 main_thread 中執行
    //在此處之前,確保執行緒池已經啟動。
    push_thread(sockfd, thread_cb); //將sockfd放到其他執行緒中執行。
}

  Handle函式是將sockfd處理方式放到另一個已經其他的執行緒中執行,如此做法,將io操作(recv,send)與epoll_wait 不在一個處理流程裡面,使得io操作(recv,send)與epoll_wait實現解耦。這就叫做IO非同步操作。
優點:
1.子模組好規劃。
2.程式效能高。
缺點:
正因為子模組好規劃,使得模組之間的sockfd的管理異常麻煩。每一個子執行緒都需要管理好sockfd,避免在IO操作的時候,sockfd出現關閉或其他異常。

  上文有提到IO同步操作,程式響應慢,IO非同步操作,程式響應快。

  下面來對比一下IO同步操作與IO非同步操作,程式碼如下:
server_mulport_epool.c
  在這份程式碼的main函式內,#if 1, 開啟的時候,為IO非同步操作。關閉的時候,為IO同步操作。

#if 1
                if (nRun) {
                    printf(" New Data is Comming\n");
                    client_data_process(clientfd);
                } else {

                    client_t *rClient = (client_t*)malloc(sizeof(client_t));
                    memset(rClient, 0, sizeof(client_t));               
                    rClient->fd = clientfd;

                    job_t *job = malloc(sizeof(job_t));
                    job->job_function = client_job;
                    job->user_data = rClient;
                    workqueue_add_job(&workqueue, job);

                }
#else
                client_data_process(clientfd);          
#endif

  接下來把我測試接入量的結果粘貼出來。

  • IO非同步操作
    IO非同步操作,每1000個連線接入的伺服器響應時間(900ms左右)。
    IO非同步操作
  • IO同步操作
    IO同步操作,每1000個連線接入的伺服器響應時間(6500ms左右)。
    IO同步操作

epoll的IO非同步操作與IO同步操作比較如下:

對比項 IO同步操作 IO非同步操作
Socket管理 管理方便 多個執行緒共同管理
程式碼邏輯 程式整體邏輯清晰 子模組邏輯清晰
IO效能 響應時間長,效能差 響應時間短,效能好

  有沒有一種方式,有非同步效能,同步的程式碼邏輯。來方便程式設計人員對IO操作的元件呢? 有,採用一種輕量級的協程來實現。在每次send或者recv之前進行切換,再由排程器來處理epoll_wait的流程。
就是採用了基於這樣的思考,寫了NtyCo,實現了一個IO非同步操作與協程結合的元件。

更多分享

email: [email protected]
email: [email protected]
email: [email protected]
協程技術交流群:829348971