日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ucontext-人人都可以实现的简单协程库

發(fā)布時間:2025/3/21 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ucontext-人人都可以实现的简单协程库 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1.干貨寫在前面

協(xié)程是一種用戶態(tài)的輕量級線程。本篇主要研究協(xié)程的C/C++的實現(xiàn)。
首先我們可以看看有哪些語言已經(jīng)具備協(xié)程語義:

  • 比較重量級的有C#、erlang、golang*
  • 輕量級有python、lua、javascript、ruby
  • 還有函數(shù)式的scala、scheme等。

c/c++不直接支持協(xié)程語義,但有不少開源的協(xié)程庫,如:
Protothreads:一個“蠅量級” C 語言協(xié)程庫
libco:來自騰訊的開源協(xié)程庫libco介紹,官網(wǎng)
coroutine:云風(fēng)的一個C語言同步協(xié)程庫,詳細信息

目前看到大概有四種實現(xiàn)協(xié)程的方式:

  • 第一種:利用glibc 的 ucontext組件(云風(fēng)的庫)
  • 第二種:使用匯編代碼來切換上下文(實現(xiàn)c協(xié)程)
  • 第三種:利用C語言語法switch-case的奇淫技巧來實現(xiàn)(Protothreads)
  • 第四種:利用了 C 語言的 setjmp 和 longjmp( 一種協(xié)程的 C/C++ 實現(xiàn),要求函數(shù)里面使用 static local 的變量來保存協(xié)程內(nèi)部的數(shù)據(jù))

本篇主要使用ucontext來實現(xiàn)簡單的協(xié)程庫。

2.ucontext初接觸

利用ucontext提供的四個函數(shù)getcontext(),setcontext(),makecontext(),swapcontext()可以在一個進程中實現(xiàn)用戶級的線程切換。

本節(jié)我們先來看ucontext實現(xiàn)的一個簡單的例子:

[cpp] view plain copy
  • #include?<stdio.h>??
  • #include?<ucontext.h>??
  • #include?<unistd.h>??
  • ??
  • int?main(int?argc,?const?char?*argv[]){??
  • ????ucontext_t?context;??
  • ??
  • ????getcontext(&context);??
  • ????puts("Hello?world");??
  • ????sleep(1);??
  • ????setcontext(&context);??
  • ????return?0;??
  • }??
  • 注:示例代碼來自維基百科.

    保存上述代碼到example.c,執(zhí)行編譯命令:

    gcc example.c -o example

    想想程序運行的結(jié)果會是什么樣?

    [plain] view plain copy
  • cxy@ubuntu:~$?./example???
  • Hello?world??
  • Hello?world??
  • Hello?world??
  • Hello?world??
  • Hello?world??
  • Hello?world??
  • Hello?world??
  • ^C??
  • cxy@ubuntu:~$??

  • 上面是程序執(zhí)行的部分輸出,不知道是否和你想得一樣呢?我們可以看到,程序在輸出第一個“Hello world"后并沒有退出程序,而是持續(xù)不斷的輸出”Hello world“。其實是程序通過getcontext先保存了一個上下文,然后輸出"Hello world",在通過setcontext恢復(fù)到getcontext的地方,重新執(zhí)行代碼,所以導(dǎo)致程序不斷的輸出”Hello world“,在我這個菜鳥的眼里,這簡直就是一個神奇的跳轉(zhuǎn)。

    那么問題來了,ucontext到底是什么?

    3.ucontext組件到底是什么

    在類System V環(huán)境中,在頭文件< ucontext.h > 中定義了兩個結(jié)構(gòu)類型,mcontext_t和ucontext_t和四個函數(shù)getcontext(),setcontext(),makecontext(),swapcontext().利用它們可以在一個進程中實現(xiàn)用戶級的線程切換。

    mcontext_t類型與機器相關(guān),并且不透明.ucontext_t結(jié)構(gòu)體則至少擁有以下幾個域:

    [cpp] view plain copy
  • typedef?struct?ucontext?{??
  • ????struct?ucontext?*uc_link;??
  • ????sigset_t?????????uc_sigmask;??
  • ????stack_t??????????uc_stack;??
  • ????mcontext_t???????uc_mcontext;??
  • ????...??
  • }?ucontext_t;??
  • 當(dāng)當(dāng)前上下文(如使用makecontext創(chuàng)建的上下文)運行終止時系統(tǒng)會恢復(fù)uc_link指向的上下文;uc_sigmask為該上下文中的阻塞信號集合;uc_stack為該上下文中使用的棧;uc_mcontext保存的上下文的特定機器表示,包括調(diào)用線程的特定寄存器等。

    下面詳細介紹四個函數(shù):

    int getcontext(ucontext_t *ucp);

    初始化ucp結(jié)構(gòu)體,將當(dāng)前的上下文保存到ucp中

    int setcontext(const ucontext_t *ucp);

    設(shè)置當(dāng)前的上下文為ucp,setcontext的上下文ucp應(yīng)該通過getcontext或者makecontext取得,如果調(diào)用成功則不返回。如果上下文是通過調(diào)用getcontext()取得,程序會繼續(xù)執(zhí)行這個調(diào)用。如果上下文是通過調(diào)用makecontext取得,程序會調(diào)用makecontext函數(shù)的第二個參數(shù)指向的函數(shù),如果func函數(shù)返回,則恢復(fù)makecontext第一個參數(shù)指向的上下文第一個參數(shù)指向的上下文context_t中指向的uc_link.如果uc_link為NULL,則線程退出。

    void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

    makecontext修改通過getcontext取得的上下文ucp(這意味著調(diào)用makecontext前必須先調(diào)用getcontext)。然后給該上下文指定一個棧空間ucp->stack,設(shè)置后繼的上下文ucp->uc_link.

    當(dāng)上下文通過setcontext或者swapcontext激活后,執(zhí)行func函數(shù),argc為func的參數(shù)個數(shù),后面是func的參數(shù)序列。當(dāng)func執(zhí)行返回后,繼承的上下文被激活,如果繼承上下文為NULL時,線程退出。

    int swapcontext(ucontext_t *oucp, ucontext_t *ucp);

    保存當(dāng)前上下文到oucp結(jié)構(gòu)體中,然后激活upc上下文。

    如果執(zhí)行成功,getcontext返回0,setcontext和swapcontext不返回;如果執(zhí)行失敗,getcontext,setcontext,swapcontext返回-1,并設(shè)置對于的errno.

    簡單說來, getcontext獲取當(dāng)前上下文,setcontext設(shè)置當(dāng)前上下文,swapcontext切換上下文,makecontext創(chuàng)建一個新的上下文。

    4.小試牛刀-使用ucontext組件實現(xiàn)線程切換

    雖然我們稱協(xié)程是一個用戶態(tài)的輕量級線程,但實際上多個協(xié)程同屬一個線程。任意一個時刻,同一個線程不可能同時運行兩個協(xié)程。如果我們將協(xié)程的調(diào)度簡化為:主函數(shù)調(diào)用協(xié)程1,運行協(xié)程1直到協(xié)程1返回主函數(shù),主函數(shù)在調(diào)用協(xié)程2,運行協(xié)程2直到協(xié)程2返回主函數(shù)。示意步驟如下:

    [cpp] view plain copy
  • 執(zhí)行主函數(shù)??
  • 切換:主函數(shù)?-->?協(xié)程1??
  • 執(zhí)行協(xié)程1??
  • 切換:協(xié)程1??-->?主函數(shù)??
  • 執(zhí)行主函數(shù)??
  • 切換:主函數(shù)?-->?協(xié)程2??
  • 執(zhí)行協(xié)程2??
  • 切換協(xié)程2??-->?主函數(shù)??
  • 執(zhí)行主函數(shù)??
  • ...??
  • 這種設(shè)計的關(guān)鍵在于實現(xiàn)主函數(shù)到一個協(xié)程的切換,然后從協(xié)程返回主函數(shù)。這樣無論是一個協(xié)程還是多個協(xié)程都能夠完成與主函數(shù)的切換,從而實現(xiàn)協(xié)程的調(diào)度。

    實現(xiàn)用戶線程的過程是:

  • 我們首先調(diào)用getcontext獲得當(dāng)前上下文
  • 修改當(dāng)前上下文ucontext_t來指定新的上下文,如指定棧空間極其大小,設(shè)置用戶線程執(zhí)行完后返回的后繼上下文(即主函數(shù)的上下文)等
  • 調(diào)用makecontext創(chuàng)建上下文,并指定用戶線程中要執(zhí)行的函數(shù)
  • 切換到用戶線程上下文去執(zhí)行用戶線程(如果設(shè)置的后繼上下文為主函數(shù),則用戶線程執(zhí)行完后會自動返回主函數(shù))。
  • 下面代碼context_test函數(shù)完成了上面的要求。

    [cpp] view plain copy
  • #include?<ucontext.h>??
  • #include?<stdio.h>??
  • ??
  • void?func1(void?*?arg)??
  • {??
  • ????puts("1");??
  • ????puts("11");??
  • ????puts("111");??
  • ????puts("1111");??
  • ??
  • }??
  • void?context_test()??
  • {??
  • ????char?stack[1024*128];??
  • ????ucontext_t?child,main;??
  • ??
  • ????getcontext(&child);?//獲取當(dāng)前上下文??
  • ????child.uc_stack.ss_sp?=?stack;//指定棧空間??
  • ????child.uc_stack.ss_size?=?sizeof(stack);//指定棧空間大小??
  • ????child.uc_stack.ss_flags?=?0;??
  • ????child.uc_link?=?&main;//設(shè)置后繼上下文??
  • ??
  • ????makecontext(&child,(void?(*)(void))func1,0);//修改上下文指向func1函數(shù)??
  • ??
  • ????swapcontext(&main,&child);//切換到child上下文,保存當(dāng)前上下文到main??
  • ????puts("main");//如果設(shè)置了后繼上下文,func1函數(shù)指向完后會返回此處??
  • }??
  • ??
  • int?main()??
  • {??
  • ????context_test();??
  • ??
  • ????return?0;??
  • }??
  • 在context_test中,創(chuàng)建了一個用戶線程child,其運行的函數(shù)為func1.指定后繼上下文為main
    func1返回后激活后繼上下文,繼續(xù)執(zhí)行主函數(shù)。

    保存上面代碼到example-switch.cpp.運行編譯命令:

    g++ example-switch.cpp -o example-switch

    執(zhí)行程序結(jié)果如下

    [cpp] view plain copy
  • cxy@ubuntu:~$?./example-switch??
  • 1??
  • 11??
  • 111??
  • 1111??
  • main??
  • cxy@ubuntu:~$??

  • 你也可以通過修改后繼上下文的設(shè)置,來觀察程序的行為。如修改代碼 child.uc_link = &main;

    child.uc_link = NULL;

    再重新編譯執(zhí)行,其執(zhí)行結(jié)果為:

    [cpp] view plain copy
  • cxy@ubuntu:~$?./example-switch??
  • 1??
  • 11??
  • 111??
  • 1111??
  • cxy@ubuntu:~$??
  • 可以發(fā)現(xiàn)程序沒有打印"main",執(zhí)行為func1后直接退出,而沒有返回主函數(shù)。可見,如果要實現(xiàn)主函數(shù)到線程的切換并返回,指定后繼上下文是非常重要的。

    5.使用ucontext實現(xiàn)自己的線程庫

    掌握了上一節(jié)從主函數(shù)到協(xié)程的切換的關(guān)鍵,我們就可以開始考慮實現(xiàn)自己的協(xié)程了。
    定義一個協(xié)程的結(jié)構(gòu)體如下:

    [cpp] view plain copy
  • typedef?void?(*Fun)(void?*arg);??
  • ??
  • typedef?struct?uthread_t??
  • {??
  • ????ucontext_t?ctx;??
  • ????Fun?func;??
  • ????void?*arg;??
  • ????enum?ThreadState?state;??
  • ????char?stack[DEFAULT_STACK_SZIE];??
  • }uthread_t;??
  • ctx保存協(xié)程的上下文,stack為協(xié)程的棧,棧大小默認為DEFAULT_STACK_SZIE=128Kb.你可以根據(jù)自己的需求更改棧的大小。func為協(xié)程執(zhí)行的用戶函數(shù),arg為func的參數(shù),state表示協(xié)程的運行狀態(tài),包括FREE,RUNNABLE,RUNING,SUSPEND,分別表示空閑,就緒,正在執(zhí)行和掛起四種狀態(tài)。

    在定義一個調(diào)度器的結(jié)構(gòu)體

    [cpp] view plain copy
  • typedef?std::vector<uthread_t>?Thread_vector;??
  • ??
  • typedef?struct?schedule_t??
  • {??
  • ????ucontext_t?main;??
  • ????int?running_thread;??
  • ????Thread_vector?threads;??
  • ??
  • ????schedule_t():running_thread(-1){}??
  • }schedule_t;??
  • 調(diào)度器包括主函數(shù)的上下文main,包含當(dāng)前調(diào)度器擁有的所有協(xié)程的vector類型的threads,以及指向當(dāng)前正在執(zhí)行的協(xié)程的編號running_thread.如果當(dāng)前沒有正在執(zhí)行的協(xié)程時,running_thread=-1.

    接下來,在定義幾個使用函數(shù)uthread_create,uthread_yield,uthread_resume函數(shù)已經(jīng)輔助函數(shù)schedule_finished.就可以了。

    int uthread_create(schedule_t &schedule,Fun func,void *arg);

    創(chuàng)建一個協(xié)程,該協(xié)程的會加入到schedule的協(xié)程序列中,func為其執(zhí)行的函數(shù),arg為func的執(zhí)行函數(shù)。返回創(chuàng)建的線程在schedule中的編號。

    void uthread_yield(schedule_t &schedule);

    掛起調(diào)度器schedule中當(dāng)前正在執(zhí)行的協(xié)程,切換到主函數(shù)。

    void uthread_resume(schedule_t &schedule,int id);

    恢復(fù)運行調(diào)度器schedule中編號為id的協(xié)程

    int schedule_finished(const schedule_t &schedule);

    判斷schedule中所有的協(xié)程是否都執(zhí)行完畢,是返回1,否則返回0.注意:如果有協(xié)程處于掛起狀態(tài)時算作未全部執(zhí)行完畢,返回0.

    代碼就不全貼出來了,我們來看看兩個關(guān)鍵的函數(shù)的具體實現(xiàn)。首先是uthread_resume函數(shù):

    [cpp] view plain copy
  • void?uthread_resume(schedule_t?&schedule?,?int?id)??
  • {??
  • ????if(id?<?0?||?id?>=?schedule.threads.size()){??
  • ????????return;??
  • ????}??
  • ??
  • ????uthread_t?*t?=?&(schedule.threads[id]);??
  • ??
  • ????switch(t->state){??
  • ????????case?RUNNABLE:??
  • ????????????getcontext(&(t->ctx));??
  • ??
  • ????????????t->ctx.uc_stack.ss_sp?=?t->stack;??
  • ????????????t->ctx.uc_stack.ss_size?=?DEFAULT_STACK_SZIE;??
  • ????????????t->ctx.uc_stack.ss_flags?=?0;??
  • ????????????t->ctx.uc_link?=?&(schedule.main);??
  • ????????????t->state?=?RUNNING;??
  • ??
  • ????????????schedule.running_thread?=?id;??
  • ??
  • ????????????makecontext(&(t->ctx),(void?(*)(void))(uthread_body),1,&schedule);??
  • ??
  • ????????????/*?!!?note?:?Here?does?not?need?to?break?*/??
  • ??
  • ????????case?SUSPEND:??
  • ??
  • ????????????swapcontext(&(schedule.main),&(t->ctx));??
  • ??
  • ????????????break;??
  • ????????default:?;??
  • ????}??
  • }??
  • 如果指定的協(xié)程是首次運行,處于RUNNABLE狀態(tài),則創(chuàng)建一個上下文,然后切換到該上下文。如果指定的協(xié)程已經(jīng)運行過,處于SUSPEND狀態(tài),則直接切換到該上下文即可。代碼中需要注意RUNNBALE狀態(tài)的地方不需要break.

    [cpp] view plain copy
  • void?uthread_yield(schedule_t?&schedule)??
  • {??
  • ????if(schedule.running_thread?!=?-1?){??
  • ????????uthread_t?*t?=?&(schedule.threads[schedule.running_thread]);??
  • ????????t->state?=?SUSPEND;??
  • ????????schedule.running_thread?=?-1;??
  • ??
  • ????????swapcontext(&(t->ctx),&(schedule.main));??
  • ????}??
  • }??
  • uthread_yield掛起當(dāng)前正在運行的協(xié)程。首先是將running_thread置為-1,將正在運行的協(xié)程的狀態(tài)置為SUSPEND,最后切換到主函數(shù)上下文。

    更具體的代碼我已經(jīng)放到github上,點擊這里。

    6.最后一步-使用我們自己的協(xié)程庫

    保存下面代碼到example-uthread.cpp.

    [cpp] view plain copy
  • #include?"uthread.h"??
  • #include?<stdio.h>??
  • ??
  • void?func2(void?*?arg)??
  • {??
  • ????puts("22");??
  • ????puts("22");??
  • ????uthread_yield(*(schedule_t?*)arg);??
  • ????puts("22");??
  • ????puts("22");??
  • }??
  • ??
  • void?func3(void?*arg)??
  • {??
  • ????puts("3333");??
  • ????puts("3333");??
  • ????uthread_yield(*(schedule_t?*)arg);??
  • ????puts("3333");??
  • ????puts("3333");??
  • ??
  • }??
  • ??
  • void?schedule_test()??
  • {??
  • ????schedule_t?s;??
  • ??
  • ????int?id1?=?uthread_create(s,func3,&s);??
  • ????int?id2?=?uthread_create(s,func2,&s);??
  • ??
  • ????while(!schedule_finished(s)){??
  • ????????uthread_resume(s,id2);??
  • ????????uthread_resume(s,id1);??
  • ????}??
  • ????puts("main?over");??
  • ??
  • }??
  • int?main()??
  • {??
  • ????schedule_test();??
  • ??
  • ????return?0;??
  • }??
  • 執(zhí)行編譯命令并運行:

    g++ example-uthread.cpp -o example-uthread ./example-uthread

    運行結(jié)果如下:

    [cpp] view plain copy
  • cxy@ubuntu:~/mythread$./example-uthread??
  • 22??
  • 22??
  • 3333??
  • 3333??
  • 22??
  • 22??
  • 3333??
  • 3333??
  • main?over??
  • cxy@ubuntu:~/mythread$??
  • 可以看到,程序協(xié)程func2,然后切換到主函數(shù),在執(zhí)行協(xié)程func3,再切換到主函數(shù),又切換到func2,在切換到主函數(shù),再切換到func3,最后切換到主函數(shù)結(jié)束。

    總結(jié)一下,我們利用getcontext和makecontext創(chuàng)建上下文,設(shè)置后繼的上下文到主函數(shù),設(shè)置每個協(xié)程的棧空間。在利用swapcontext在主函數(shù)和協(xié)程之間進行切換。

    到此,使用ucontext做一個自己的協(xié)程庫就到此結(jié)束了。相信你也可以自己完成自己的協(xié)程庫了。

    最后,代碼我已經(jīng)放到github上,點擊這里。

    總結(jié)

    以上是生活随笔為你收集整理的ucontext-人人都可以实现的简单协程库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 国产白丝袜美女久久久久 | 三级av片 | 射综合网 | 男女搞黄网站 | 就操网 | 精品影视一区二区 | 中国少妇初尝黑人巨大 | 中午字幕在线观看 | 美女视频黄免费 | 天天操天天看 | 精品人伦一区二区三区 | 亚洲精品7777 | 精品免费一区二区 | 97自拍网| 日韩成人精品一区二区 | 欧美无人区码suv | 日本aⅴ视频 | 中文字幕日韩欧美一区二区三区 | 全部免费毛片在线播放一个 | 黄色一级免费片 | 成人黄色视屏 | 快色网站 | 囯产精品一品二区三区 | 国产午夜精品一区二区 | 在线观看视频福利 | 日韩午夜伦 | 免费无码又爽又黄又刺激网站 | 欧产日产国产精品 | 成年激情网 | 中文字母av| 欧日韩不卡在线视频 | 毛片无遮挡 | 色天使亚洲 | 国产精品一二三级 | 亚洲精品久久夜色撩人男男小说 | 欧美成人第一页 | 性欧美18一19性猛交 | 日本激情一区二区 | 美女黄色一级视频 | 色呦呦免费视频 | 午夜视频免费在线观看 | 精品视频一区二区在线观看 | 放荡闺蜜高h苏桃情事h | 一级片大片 | 亚洲第一国产 | 精品无码人妻少妇久久久久久 | 亚洲成人乱码 | 成年人在线观看视频网站 | 久久免费视频6 | 国产无码精品久久久 | av久热| 性欧美成人播放77777 | 日本人妻伦在线中文字幕 | 中文字幕视频在线 | 天天操夜夜添 | 91av一区| 国内精品一区二区三区 | 国产三级按摩推拿按摩 | 波多野结衣一区二区三区四区 | 91亚洲国产成人精品性色 | 国产一级二级在线观看 | 精品久久久一区二区 | 涩涩涩av | 黄色av网站在线看 | 国产综合内射日韩久 | 亚洲视频在线观看一区二区 | 伊人影院在线播放 | 亚洲欧美日韩中文字幕在线观看 | 老狼影院伦理片 | 无码人妻精品一区二区三区温州 | 2023毛片| 亚洲精品成人 | 日韩成人在线视频观看 | 精品久久一区二区三区 | 日本a∨视频 | 在线免费观看日韩av | 成人精品免费在线观看 | 亚洲理论视频 | 国产真实乱人偷精品视频 | 欧美日韩亚洲综合 | 视频久久| av不卡在线免费观看 | 牛牛在线免费视频 | 巨大乳の揉んで乳榨り奶水 | 插吧插吧网| 级毛片 | 日韩精品免费一区二区三区 | 波多野结衣免费看 | 在线日韩| 免费久久一级欧美特大黄 | 男人天堂视频网 | 精品在线视频播放 | 在线观看涩涩视频 | 四虎国产成人永久精品免费 | 欧美另类z0zx974 | 懂色一区二区二区av免费观看 | 成人αv| 91久久国产综合久久91精品网站 | 日本老太婆做爰视频 |