色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

協程的實現與原理

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-10 10:57 ? 次閱讀

前言

協程這個概念很久了,好多程序員是實現過這個組件的,網上關于協程的文章,博客,論壇都是汗牛充棟,在知乎,github上面也有很多大牛寫了關于協程的心得體會。突發奇想,我也來實現一個這樣的組件,并測試了一下性能。借鑒了很多大牛的思想,閱讀了很多大牛的代碼。于是把整個思考過程寫下來。實現代碼

https://github.com/wangbojing/NtyCotyCo

代碼簡單易讀,如果在你的項目中,NtyCo能夠為你解決些許工程問題,那就榮幸之至。

本文章的設計思路,是在每一個章的最前面以問題提出,每章節的學習目的。大家能夠帶著每章的問題來讀每章節的內容,方便讀者能夠方便的進入每章節的思考。讀者讀完以后加上案例代碼閱讀,編譯,運行,能夠對神秘的協程有一個全新的理解。能夠運用到工程代碼,幫助你更加方便高效的完成工程工作。

第一章 協程的起源

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

在我們現在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異步操作。

代碼如下:

https://github.com/wangbojing/c1000k_test/blob/master/server_mulport_epoll.c

在這份代碼的486行,#if 1, 打開的時候,為IO異步操作。關閉的時候,為IO同步操作。

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

IO異步操作,每1000個連接接入的服務器響應時間(900ms左右)。

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

IO異步操作與IO同步操作

對比項

IO同步操作

IO異步操作

Sockfd管理

管理方便

多個線程共同管理

代碼邏輯

程序整體邏輯清晰

子模塊邏輯清晰

程序性能

響應時間長,性能差

響應時間短,性能好

有沒有一種方式,有異步性能,同步的代碼邏輯。來方便編程人員對IO操作的組件呢?有,采用一種輕量級的協程來實現。在每次send或者recv之前進行切換,再由調度器來處理epoll_wait的流程。

就是采用了基于這樣的思考,寫了NtyCo,實現了一個IO異步操作與協程結合的組件。https://https://github.com/wangbojing/NtyCo

第二章 協程的案例

問題:協程如何使用?與線程使用有何區別?

在做網絡IO編程的時候,有一個非常理想的情況,就是每次accept返回的時候,就為新來的客戶端分配一個線程,這樣一個客戶端對應一個線程。就不會有多個線程共用一個sockfd。每請求每線程的方式,并且代碼邏輯非常易讀。但是這只是理想,線程創建代價,調度代價就呵呵了。

先來看一下每請求每線程的代碼如下:

while(1) {

 socklen_t len = sizeof(struct sockaddr_in);

 int clientfd = accept(sockfd, (struct sockaddr*)&remote, &len);



 pthread_t thread_id;

 pthread_create(&thread_id, NULL, client_cb, &clientfd);



}

這樣的做法,寫完放到生產環境下面,如果你的老板不打死你,你來找我。我來幫你老板,為民除害。

如果我們有協程,我們就可以這樣實現。參考代碼如下:

https://github.com/wangbojing/NtyCo/blob/master/nty_server_test.c


while (1) {

    socklen_t len = sizeof(struct sockaddr_in);

    int cli_fd = nty_accept(fd, (struct sockaddr*)&remote, &len);



    nty_coroutine *read_co;

    nty_coroutine_create(&read_co, server_reader, &cli_fd);



}

這樣的代碼是完全可以放在生成環境下面的。如果你的老板要打死你,你來找我,我幫你把你老板打死,為民除害。

線程的API思維來使用協程,函數調用的性能來測試協程。

NtyCo封裝出來了若干接口,一類是協程本身的,二類是posix的異步封裝

協程API:while

  1. 協程創建

int nty_coroutine_create(nty_coroutine **new_co, proc_coroutine func, void *arg)

  1. 協程調度器的運行

void nty_schedule_run(void)

POSIX異步封裝API:

int nty_socket(int domain, int type, int protocol)

int nty_accept(int fd, struct sockaddr *addr, socklen_t *len)

int nty_recv(int fd, void *buf, int length)

int nty_send(int fd, const void *buf, int length)

int nty_close(int fd)

接口格式與POSIX標準的函數定義一致。

第三章 協程的實現之工作流程

問題:協程內部是如何工作呢?

先來看一下協程服務器案例的代碼, 代碼參考:https://github.com/wangbojing/NtyCo/blob/master/nty_server_test.c

分別討論三個協程的比較晦澀的工作流程。第一個協程的創建;第二個IO異步操作;第三個協程子過程回調

3.1 創建協程

當我們需要異步調用的時候,我們會創建一個協程。比如accept返回一個新的sockfd,創建一個客戶端處理的子過程。再比如需要監聽多個端口的時候,創建一個server的子過程,這樣多個端口同時工作的,是符合微服務的架構的。

創建協程的時候,進行了如何的工作?創建API如下:

int nty_coroutine_create(nty_coroutine **new_co, proc_coroutine func, void *arg)

參數1:nty_coroutine **new_co,需要傳入空的協程的對象,這個對象是由內部創建的,并且在函數返回的時候,會返回一個內部創建的協程對象。

參數2:proc_coroutine func,協程的子過程。當協程被調度的時候,就會執行該函數。

參數3:void *arg,需要傳入到新協程中的參數。

協程不存在親屬關系,都是一致的調度關系,接受調度器的調度。調用create API就會創建一個新協程,新協程就會加入到調度器的就緒隊列中。

創建的協程具體步驟會在《協程的實現之原語操作》來描述。

3.2 實現IO異步操作

大部分的朋友會關心IO異步操作如何實現,在send與recv調用的時候,如何實現異步操作的。

先來看一下一段代碼:

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 {



            epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);

 recv(sockfd, buffer, length, 0);



 //parser_proto(buffer, length);



 send(sockfd, buffer, length, 0);

            epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, NULL);

        }

    }

}

在進行IO操作(recv,send)之前,先執行了 epoll_ctl的del操作,將相應的sockfd從epfd中刪除掉,在執行完IO操作(recv,send)再進行epoll_ctl的add的動作。這段代碼看起來似乎好像沒有什么作用。

如果是在多個上下文中,這樣的做法就很有意義了。能夠保證sockfd只在一個上下文中能夠操作IO的。不會出現在多個上下文同時對一個IO進行操作的。協程的IO異步操作正式是采用此模式進行的。

把單一協程的工作與調度器的工作的劃分清楚,先引入兩個原語操作 resume,yield會在《協程的實現之原語操作》來講解協程所有原語操作的實現,yield就是讓出運行,resume就是恢復運行。調度器與協程的上下文切換如下圖所示

在協程的上下文IO異步操作(nty_recv,nty_send)函數,步驟如下:

  1. 將sockfd 添加到epoll管理中。
  2. 進行上下文環境切換,由協程上下文yield到調度器的上下文。
  3. 調度器獲取下一個協程上下文。Resume新的協程

IO異步操作的上下文切換的時序圖如下:

3.3 回調協程的子過程

在create協程后,何時回調子過程?何種方式回調子過程?

首先來回顧一下x86_64寄存器的相關知識。匯編與寄存器相關知識還會在《協程的實現之切換》繼續深入探討的。x86_64 的寄存器有16個64位寄存器,分別是:%rax, %rbx,

%rcx, %esi, %edi, %rbp, %rsp, %r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15。

%rax 作為函數返回值使用的。

%rsp 棧指針寄存器,指向棧頂

%rdi, %rsi, %rdx, %rcx, %r8, %r9 用作函數參數,依次對應第1參數,第2參數。。。

%rbx, %rbp, %r12, %r13, %r14, %r15 用作數據存儲,遵循調用者使用規則,換句話說,就是隨便用。調用子函數之前要備份它,以防它被修改

%r10, %r11 用作數據存儲,就是使用前要先保存原值

以NtyCo的實現為例,來分析這個過程。CPU有一個非常重要的寄存器叫做EIP,用來存儲CPU運行下一條指令的地址。我們可以把回調函數的地址存儲到EIP中,將相應的參數存儲到相應的參數寄存器中。實現子過程調用的邏輯代碼如下:

void _exec(nty_coroutine *co) {

    co- >func(co- >arg); //子過程的回調函數

}



void nty_coroutine_init(nty_coroutine *co) {

 //ctx 就是協程的上下文

    co- >ctx.edi = (void*)co; //設置參數

    co- >ctx.eip = (void*)_exec; //設置回調函數入口

 //當實現上下文切換的時候,就會執行入口函數_exec , _exec 調用子過程func

}

第四章 協程的實現之原語操作

問題:協程的內部原語操作有哪些?分別如何實現的?

協程的核心原語操作:create, resume, yield。協程的原語操作有create怎么沒有exit?以NtyCo為例,協程一旦創建就不能有用戶自己銷毀,必須得以子過程執行結束,就會自動銷毀協程的上下文數據。以_exec執行入口函數返回而銷毀協程的上下文與相關信息。co->func(co->arg) 是子過程,若用戶需要長久運行協程,就必須要在func函數里面寫入循環等操作。所以NtyCo里面沒有實現exit的原語操作。

create:創建一個協程。

  1. 調度器是否存在,不存在也創建。調度器作為全局的單例。將調度器的實例存儲在線程的私有空間pthread_setspecific
  2. 分配一個coroutine的內存空間,分別設置coroutine的數據項,棧空間,棧大小,初始狀態,創建時間,子過程回調函數,子過程的調用參數。
  3. 將新分配協程添加到就緒隊列 ready_queue中

實現代碼如下:

int nty_coroutine_create(nty_coroutine **new_co, proc_coroutine func, void *arg) {



    assert(pthread_once(&sched_key_once, nty_coroutine_sched_key_creator) == 0);

    nty_schedule *sched = nty_coroutine_get_sched();



 if (sched == NULL) {

        nty_schedule_create(0);



        sched = nty_coroutine_get_sched();

 if (sched == NULL) {

            printf("Failed to create schedulern");

 return -1;

        }

    }



    nty_coroutine *co = calloc(1, sizeof(nty_coroutine));

 if (co == NULL) {

        printf("Failed to allocate memory for new coroutinen");

 return -2;

    }



 //

 int ret = posix_memalign(&co- >stack, getpagesize(), sched- >stack_size);

 if (ret) {

        printf("Failed to allocate stack for new coroutinen");

        free(co);

 return -3;

    }



    co- >sched = sched;

    co- >stack_size = sched- >stack_size;

    co- >status = BIT(NTY_COROUTINE_STATUS_NEW); //

    co- >id = sched- >spawned_coroutines ++;

co- >func = func;



    co- >fd = -1;

co- >events = 0;



    co- >arg = arg;

    co- >birth = nty_coroutine_usec_now();

    *new_co = co;



    TAILQ_INSERT_TAIL(&co- >sched- >ready, co, ready_next);



 return 0;

}

yield:讓出CPU。

void nty_coroutine_yield(nty_coroutine *co)

參數:當前運行的協程實例

調用后該函數不會立即返回,而是切換到最近執行resume的上下文。該函數返回是在執行resume的時候,會有調度器統一選擇resume的,然后再次調用yield的。resume與yield是兩個可逆過程的原子操作。

resume:恢復協程的運行權

int nty_coroutine_resume(nty_coroutine *co)

參數:需要恢復運行的協程實例

調用后該函數也不會立即返回,而是切換到運行協程實例的yield的位置。返回是在等協程相應事務處理完成后,主動yield會返回到resume的地方。

第五章 協程的實現之切換

問題:協程的上下文如何切換?切換代碼如何實現?

首先來回顧一下x86_64寄存器的相關知識。x86_64 的寄存器有16個64位寄存器,分別是:%rax, %rbx, %rcx, %esi, %edi, %rbp, %rsp, %r8, %r9, %r10, %r11, %r12,

%r13, %r14, %r15。

%rax 作為函數返回值使用的。

%rsp 棧指針寄存器,指向棧頂

%rdi, %rsi, %rdx, %rcx, %r8, %r9 用作函數參數,依次對應第1參數,第2參數。。。

%rbx, %rbp, %r12, %r13, %r14, %r15 用作數據存儲,遵循調用者使用規則,換句話說,就是隨便用。調用子函數之前要備份它,以防它被修改

%r10, %r11 用作數據存儲,就是使用前要先保存原值。

上下文切換,就是將CPU的寄存器暫時保存,再將即將運行的協程的上下文寄存器,分別mov到相對應的寄存器上。此時上下文完成切換。如下圖所示:

切換_switch函數定義:

int _switch(nty_cpu_ctx *new_ctx, nty_cpu_ctx *cur_ctx);

參數1:即將運行協程的上下文,寄存器列表

參數2:正在運行協程的上下文,寄存器列表

我們nty_cpu_ctx結構體的定義,為了兼容x86,結構體項命令采用的是x86的寄存器名字命名。

typedef struct _nty_cpu_ctx {

void *esp; //

void *ebp;

void *eip;

void *edi;

void *esi;

void *ebx;

void *r1;

void *r2;

void *r3;

void *r4;

void *r5;

} nty_cpu_ctx;

_switch返回后,執行即將運行協程的上下文。是實現上下文的切換

_switch的實現代碼:

0: __asm__ (

1: "    .text                                  n"

2: "       .p2align 4,,15                                   n"

3: ".globl _switch                                          n"

4: ".globl __switch                                         n"

5: "_switch:                                                n"

6: "__switch:                                               n"

7: "       movq %rsp, 0(%rsi)      # save stack_pointer     n"

8: "       movq %rbp, 8(%rsi)      # save frame_pointer     n"

9: "       movq (%rsp), %rax       # save insn_pointer      n"

10: "       movq %rax, 16(%rsi)                              n"

11: "       movq %rbx, 24(%rsi)     # save rbx,r12-r15       n"

12: "       movq %r12, 32(%rsi)                              n"

13: "       movq %r13, 40(%rsi)                              n"

14: "       movq %r14, 48(%rsi)                              n"

15: "       movq %r15, 56(%rsi)                              n"

16: "       movq 56(%rdi), %r15                              n"

17: "       movq 48(%rdi), %r14                              n"

18: "       movq 40(%rdi), %r13     # restore rbx,r12-r15    n"

19: "       movq 32(%rdi), %r12                              n"

20: "       movq 24(%rdi), %rbx                              n"

21: "       movq 8(%rdi), %rbp      # restore frame_pointer  n"

22: "       movq 0(%rdi), %rsp      # restore stack_pointer  n"

23: "       movq 16(%rdi), %rax     # restore insn_pointer   n"

24: "       movq %rax, (%rsp)                                n"

25: "       ret                                              n"

26: );

按照x86_64的寄存器定義,%rdi保存第一個參數的值,即new_ctx的值,%rsi保存第二個參數的值,即保存cur_ctx的值。X86_64每個寄存器是64bit,8byte。

Movq %rsp, 0(%rsi) 保存在棧指針到cur_ctx實例的rsp項

Movq %rbp, 8(%rsi)

Movq (%rsp), %rax #將棧頂地址里面的值存儲到rax寄存器中。Ret后出棧,執行棧頂

Movq %rbp, 8(%rsi) #后續的指令都是用來保存CPU的寄存器到new_ctx的每一項中

Movq 8(%rdi), %rbp #將new_ctx的值

Movq 16(%rdi), %rax #將指令指針rip的值存儲到rax中

Movq %rax, (%rsp) # 將存儲的rip值的rax寄存器賦值給棧指針的地址的值。

Ret # 出棧,回到棧指針,執行rip指向的指令。

上下文環境的切換完成。

第六章 協程的實現之定義

問題:協程如何定義? 調度器如何定義?

先來一道設計題:

設計一個協程的運行體R與運行體調度器S的結構體

  1. 運行體R:包含運行狀態{就緒,睡眠,等待},運行體回調函數,回調參數,棧指針,棧大小,當前運行體
  2. 調度器S:包含執行集合{就緒,睡眠,等待}

這道設計題拆分兩個個問題,一個運行體如何高效地在多種狀態集合更換。調度器與運行體的功能界限。

6.1 運行體如何高效地在多種狀態集合更換

新創建的協程,創建完成后,加入到就緒集合,等待調度器的調度;協程在運行完成后,進行IO操作,此時IO并未準備好,進入等待狀態集合;IO準備就緒,協程開始運行,后續進行sleep操作,此時進入到睡眠狀態集合。

就緒(ready),睡眠(sleep),等待(wait)集合該采用如何數據結構來存儲?

就緒(ready)集合并不沒有設置優先級的選型,所有在協程優先級一致,所以可以使用隊列來存儲就緒的協程,簡稱為就緒隊列(ready_queue)。

睡眠(sleep)集合需要按照睡眠時長進行排序,采用紅黑樹來存儲,簡稱睡眠樹(sleep_tree)紅黑樹在工程實用為, key為睡眠時長,value為對應的協程結點。

等待(wait)集合,其功能是在等待IO準備就緒,等待IO也是有時長的,所以等待(wait)集合采用紅黑樹的來存儲,簡稱等待樹(wait_tree),此處借鑒nginx的設計。

數據結構如下圖所示:

Coroutine就是協程的相應屬性,status表示協程的運行狀態。sleep與wait兩顆紅黑樹,ready使用的隊列,比如某協程調用sleep函數,加入睡眠樹(sleep_tree),status |= S即可。比如某協程在等待樹(wait_tree)中,而IO準備就緒放入ready隊列中,只需要移出等待樹(wait_tree),狀態更改status &= ~W即可。有一個前提條件就是不管何種運行狀態的協程,都在就緒隊列中,只是同時包含有其他的運行狀態。

6.2 調度器與協程的功能界限

每一協程都需要使用的而且可能會不同屬性的,就是協程屬性。每一協程都需要的而且數據一致的,就是調度器的屬性。比如棧大小的數值,每個協程都一樣的后不做更改可以作為調度器的屬性,如果每個協程大小不一致,則可以作為協程的屬性。

用來管理所有協程的屬性,作為調度器的屬性。比如epoll用來管理每一個協程對應的IO,是需要作為調度器屬性。

按照前面幾章的描述,定義一個協程結構體需要多少域,我們描述了每一個協程有自己的上下文環境,需要保存CPU的寄存器ctx;需要有子過程的回調函數func;需要有子過程回調函數的參數 arg;需要定義自己的棧空間 stack;需要有自己棧空間的大小 stack_size;需要定義協程的創建時間 birth;需要定義協程當前的運行狀態 status;需要定當前運行狀態的結點(ready_next, wait_node, sleep_node);需要定義協程id;需要定義調度器的全局對象 sched。

協程的核心結構體如下:

typedef struct _nty_coroutine {



    nty_cpu_ctx ctx;

    proc_coroutine func;

 void *arg;

 size_t stack_size;



    nty_coroutine_status status;

    nty_schedule *sched;



 uint64_t birth;

 uint64_t id;



 void *stack;



 RB_ENTRY(_nty_coroutine) sleep_node;

 RB_ENTRY(_nty_coroutine) wait_node;



 TAILQ_ENTRY(_nty_coroutine) ready_next;

 TAILQ_ENTRY(_nty_coroutine) defer_next;



} nty_coroutine;

調度器是管理所有協程運行的組件,協程與調度器的運行關系。

調度器的屬性,需要有保存CPU的寄存器上下文 ctx,可以從協程運行狀態yield到調度器運行的。從協程到調度器用yield,從調度器到協程用resume

以下為協程的定義。

typedef struct _nty_coroutine_queue nty_coroutine_queue;



typedef struct _nty_coroutine_rbtree_sleep nty_coroutine_rbtree_sleep;

typedef struct _nty_coroutine_rbtree_wait nty_coroutine_rbtree_wait;



typedef struct _nty_schedule {

 uint64_t birth;

nty_cpu_ctx ctx;



 struct _nty_coroutine *curr_thread;

 int page_size;



 int poller_fd;

 int eventfd;

 struct epoll_event eventlist[NTY_CO_MAX_EVENTS];

 int nevents;



 int num_new_events;



    nty_coroutine_queue ready;

    nty_coroutine_rbtree_sleep sleeping;

    nty_coroutine_rbtree_wait waiting;



} nty_schedule;

第七章 協程的實現之調度器

問題:協程如何被調度?

調度器的實現,有兩種方案,一種是生產者消費者模式,另一種多狀態運行。

7.1 生產者消費者模式

邏輯代碼如下:

while (1) {



 //遍歷睡眠集合,將滿足條件的加入到ready

        nty_coroutine *expired = NULL;

 while ((expired = sleep_tree_expired(sched)) != ) {

            TAILQ_ADD(&sched- >ready, expired);

        }



 //遍歷等待集合,將滿足添加的加入到ready

        nty_coroutine *wait = NULL;

 int nready = epoll_wait(sched- >epfd, events, EVENT_MAX, 1);

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

            wait = wait_tree_search(events[i].data.fd);

            TAILQ_ADD(&sched- >ready, wait);

        }



 // 使用resume回復ready的協程運行權

 while (!TAILQ_EMPTY(&sched- >ready)) {

            nty_coroutine *ready = TAILQ_POP(sched- >ready);

            resume(ready);

        }

    }

7.2 多狀態運行

實現邏輯代碼如下:

while (1) {



 //遍歷睡眠集合,使用resume恢復expired的協程運行權

        nty_coroutine *expired = NULL;

 while ((expired = sleep_tree_expired(sched)) != ) {

            resume(expired);

        }



 //遍歷等待集合,使用resume恢復wait的協程運行權

        nty_coroutine *wait = NULL;

 int nready = epoll_wait(sched- >epfd, events, EVENT_MAX, 1);

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

            wait = wait_tree_search(events[i].data.fd);

            resume(wait);

        }



 // 使用resume恢復ready的協程運行權

 while (!TAILQ_EMPTY(sched- >ready)) {

            nty_coroutine *ready = TAILQ_POP(sched- >ready);

            resume(ready);

        }

    }

第八章 協程性能測試

測試環境:4臺VMWare 虛擬機

1臺服務器 6G內存,4核CPU

3臺客戶端 2G內存,2核CPU

操作系統:ubuntu 14.04

服務器端測試代碼:https://https://github.com/wangbojing/NtyCotyCo

客戶端測試代碼:https://https://github.com/wangbojing/c1000k_test/blob/master/client_mutlport_epoll.c1000k_test/blob/master/client_mutlport_epoll.c

按照每一個連接啟動一個協程來測試。每一個協程棧空間 4096byte

6G內存 –> 測試協程數量100W無異常。并且能夠正常收發數據。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 程序
    +關注

    關注

    117

    文章

    3785

    瀏覽量

    81006
  • 代碼
    +關注

    關注

    30

    文章

    4780

    瀏覽量

    68531
  • 調度器
    +關注

    關注

    0

    文章

    98

    瀏覽量

    5245
收藏 人收藏

    評論

    相關推薦

    談談的那些事兒

    隨著異步編程的發展以及各種并發框架的普及,作為一種異步編程規范在各類語言中地位逐步提高。我們不單單會在自己的程序中使用,各類框架如fastapi,aiohttp等也都是基于異步
    的頭像 發表于 01-26 11:36 ?1111次閱讀
    談談<b class='flag-5'>協</b><b class='flag-5'>程</b>的那些事兒

    和線程有什么區別

    和線程的區別和線程的共同目的之一是實現系統資源的上下文調用,不過它們的實現層級不同;線程
    發表于 12-10 06:23

    怎樣使用C語言去實現Linux系統

    Linux系統編程練手項目:使用C語言實現 6年嵌入式開發經驗,在多家半...
    發表于 12-23 06:58

    Tars在ARM平臺上的移植是如何去實現

    計時器來實現,具體實現如下。原x86嵌匯編實現:支持ARM64平臺后的實現:3
    發表于 03-30 11:30

    Tars移植到ARM64平臺上的過程實現

    計時器來實現,具體實現如下。原x86嵌匯編實現:支持ARM64平臺后的實現:3
    發表于 07-05 14:59

    關于C++ 20最全面詳解

    花了一兩周的時間后,我想寫寫 C++20 的基本用法,因為 C++ 的讓我感到很奇怪,寫一個
    的頭像 發表于 04-12 11:10 ?1.3w次閱讀
    關于C++ 20<b class='flag-5'>協</b><b class='flag-5'>程</b>最全面詳解

    Python后端項目的是什么

    最近公司 Python 后端項目進行重構,整個后端邏輯基本都變更為采用“異步”的方式實現。看著滿屏幕經過 async await(
    的頭像 發表于 09-23 14:38 ?1327次閱讀

    Python與JavaScript的對比及經驗技巧

    對這兩個語言有興趣的新人理解和吸收。 共同訴求隨著 cpu 多核化,都需要實現由于自身歷史原因(單線程環境)下的并發功能 簡化代碼,避免回調地獄,關鍵字支持 有效利用操作系統資源和硬件:相比線程,占用資源更少,上下文更快 什
    的頭像 發表于 10-20 14:30 ?1928次閱讀

    通過例子由淺入深的理解yield

    send:send() 方法致使程前進到下一個yield 語句,另外,生成器可以作為使用
    的頭像 發表于 08-23 11:12 ?2017次閱讀

    使用channel控制數量

    goroutine 是輕量級線程,調度由 Go 運行時進行管理的。Go 語言的并發控制主要使用關鍵字 go 開啟 goroutine。Go (Goroutine)之間通過信道(
    的頭像 發表于 09-19 15:06 ?1134次閱讀

    詳解Linux線程、線程與異步編程、與異步

    不是系統級線程,很多時候被稱為“輕量級線程”、“微線程”、“纖(fiber)”等。簡單來說可以認為
    的頭像 發表于 03-16 15:49 ?978次閱讀

    的概念及的掛起函數介紹

    是一種輕量級的線程,它可以在單個線程中實現并發執行。與線程不同,不需要操作系統的上下文切換,因此可以更高效地使用系統資源。Kotli
    的頭像 發表于 04-19 10:20 ?887次閱讀

    FreeRTOS任務與介紹

    FreeRTOS 中應用既可以使用任務,也可以使用(Co-Routine),或者兩者混合使用。但是任務和協使用不同的API函數,因此不能通過隊列(或信號量)將數據從任務發送給
    的頭像 發表于 09-28 11:02 ?987次閱讀

    的作用、結構及原理

    本文介紹了的作用、結構、原理,并使用C++和匯編實現了64位系統下的池。文章內容避免了
    的頭像 發表于 11-08 16:39 ?1123次閱讀
    <b class='flag-5'>協</b><b class='flag-5'>程</b>的作用、結構及原理

    Linux線程、線程與異步編程、與異步介紹

    不是系統級線程,很多時候被稱為“輕量級線程”、“微線程”、“纖(fiber)”等。簡單來說可以認為
    的頭像 發表于 11-11 11:35 ?1148次閱讀
    Linux線程、線程與異步編程、<b class='flag-5'>協</b><b class='flag-5'>程</b>與異步介紹
    主站蜘蛛池模板: 免费观看美女的网站| 国产精品成久久久久三级四虎| 最近中文字幕MV免费高清视频8| 亚洲精品第一页| 伊人精品影院一本到综合| 亚洲午夜精品A片久久WWW解说| 亚洲精品视频在线观看视频| 夜色爽爽爽久久精品日韩| 自慰弄湿白丝袜| CHESENGAY痞帅警察GV| 俄罗斯大白屁股| 国产女合集第六部| 经典WC女厕所里TV| 蜜芽资源高清在线观看| 日韩亚洲欧美中文在线| 双腿打开揉弄高潮H苏安安秦慕深 双腿被绑成M型调教PLAY照片 | jizzjizz中国大学生| 第一会所欧美无码原创| 国产精品久久久久久久久99热| 国产在线精品亚洲视频在线| 久久精品亚洲AV高清网站性色| 欧美日韩亚洲中字二区| xxxxx中国明星18| 国产超碰精久久久久久无码AV| 狠狠爱亚洲五月婷婷av| 免费人成在线观看网站视频| 色欲AV久久综合人妻蜜桃| 在公交车上被JB草坏了被轮J了 | 香蕉在线播放| 91精品免费久久久久久久久| 国产高潮久久精品AV无码| 久久久久久免费高清电影| 三级成年网站在线观看| 又黄又湿免费高清视频| 国产成人无码视频一区二区三区| 久久精品视频在线看99| 色姣姣狠狠撩综合网| 做a爱片的全过程| 黄片在线观看| 偷上邻居熟睡少妇| china年轻小帅脸直播飞机|