這篇文章主要講解了“如何用C語言代碼實(shí)現(xiàn)協(xié)程”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何用C語言代碼實(shí)現(xiàn)協(xié)程”吧!
創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比博州網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式博州網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋博州地區(qū)。費(fèi)用合理售后完善,十年實(shí)體公司更值得信賴。
協(xié)程是一種用戶空間的非搶占式線程,主要用來解決等待大量的IO操作的問題。
對比使用多線程來解決IO阻塞任務(wù),使用協(xié)程的好處是不用加鎖,訪問共享的數(shù)據(jù)不用進(jìn)行同步操作。這里需要說明的一點(diǎn)是,使用協(xié)程之所以不需要加鎖不是因?yàn)樗械膮f(xié)程只在一個(gè)線程中運(yùn)行,而是因?yàn)閰f(xié)程的非搶占式的特點(diǎn)。也就是說,使用協(xié)程的話,在沒主動(dòng)交出CPU之前都是不會(huì)被突然切換到其它協(xié)程上的。而線程是搶占式的,使用多線程你是不能確定你的線程什么時(shí)候被操作系統(tǒng)調(diào)度,什么時(shí)候被切換,因此需要用鎖到實(shí)現(xiàn)一種“原子操作”的語義。
其實(shí)更一般更常見的做法是,使用非阻塞的IO(比如是異步IO,又或者是在syscall上自己實(shí)現(xiàn)的一套異步IO,如asio)并且將處理操作寫在回調(diào)函數(shù)中。這樣的做法一般沒什么問題,但當(dāng)回調(diào)函數(shù)變多,一段連貫的業(yè)務(wù)代碼就會(huì)被拆分到多個(gè)回調(diào)函數(shù)之中,增加維護(hù)的成本。因此使用協(xié)程可以用同步的寫法寫出效果相當(dāng)于是異步的代碼。
要實(shí)現(xiàn)一個(gè)協(xié)程,主要的問題是如何保存函數(shù)調(diào)用的上下文。實(shí)現(xiàn)代碼如下:
#define crBegin static int _cr_state = 0; switch(_cr_state) { case 0: #define crReturn(x) do { _cr_state = __LINE__; return x; case __LINE__:; } while (0) #define crFinish } int func1() { crBegin while (1) { printf("hello world\n"); crReturn(0); } crFinish }
這個(gè)代碼利用了函數(shù)的static變量來保存函數(shù)調(diào)用狀態(tài)。注意,由于vs2013有一個(gè)調(diào)試特性,所以vs2013的__LINE__的實(shí)現(xiàn)不是常量因此會(huì)編譯不通過,使用gcc就可以編譯。這段代碼簡單是簡單但是有問題,比如說如果兩個(gè)協(xié)程調(diào)用同一個(gè)函數(shù),就會(huì)出錯(cuò)。因此博客里面提及這段代碼主要是給出一個(gè)思路,如果實(shí)際使用的話這樣子肯定是不行的。
前面說過,實(shí)現(xiàn)協(xié)程最主要的是保存函數(shù)的調(diào)用的上下文,而這些上下文主要就兩個(gè)部分:1.各個(gè)寄存器的值,2.函數(shù)調(diào)用棧。C語言里可以通過setjmp來保存函數(shù)調(diào)用時(shí),各寄存器的值。保存之后,便可以通過longjmp重現(xiàn)回到當(dāng)初setjmp的地方(可以理解成跨函數(shù)的goto)。但是,需要注意的是,setjmp僅負(fù)責(zé)保存寄存器的值,不負(fù)責(zé)維護(hù)其函數(shù)調(diào)用棧(這個(gè)看看setjmp的jmp_buf的結(jié)構(gòu)就知道了),因此必須由使用者來手動(dòng)的維護(hù)這個(gè)函數(shù)調(diào)用棧。使用setjmp、longjmp的一個(gè)常見的錯(cuò)誤就是,嘗試去longjmp到一個(gè)已經(jīng)執(zhí)行完的函數(shù),這時(shí)候雖然寄存器的值是當(dāng)時(shí)保存的值,但是調(diào)用棧已經(jīng)不是原來的調(diào)用棧了。
而我的做法是,在創(chuàng)建一個(gè)協(xié)程的時(shí)候在堆上申請一塊空間(大小為2M)作為協(xié)程的調(diào)用棧,然后在setjmp的時(shí)候,手動(dòng)更改寄存器esp的值,使其指向這個(gè)我自己創(chuàng)建的調(diào)用棧。因此在以后運(yùn)行的時(shí)候,這個(gè)協(xié)程就會(huì)使用我提供的那塊內(nèi)存作為棧。
我的這個(gè)協(xié)程庫提供了三個(gè)接口:
coro_new:創(chuàng)建一個(gè)協(xié)程
coro_yield:將控制權(quán)返回給調(diào)度協(xié)程
coro_main:運(yùn)行調(diào)度協(xié)程
協(xié)程的控制流程如下:
通過coro_main運(yùn)行調(diào)度協(xié)程,并找出下一個(gè)運(yùn)行的協(xié)程,運(yùn)行之。
運(yùn)行這個(gè)協(xié)程直到其調(diào)用coro_yield將控制權(quán)返還給調(diào)度協(xié)程。
重復(fù)以上兩個(gè)步驟,直到所有協(xié)程運(yùn)行完畢。
這個(gè)協(xié)程庫實(shí)現(xiàn)的非常簡單,只有100來行的代碼,當(dāng)然實(shí)現(xiàn)它的目的是為了提供一個(gè)最簡單的協(xié)程模型,而不是一個(gè)功能完整、魯棒性強(qiáng)的能投入實(shí)際業(yè)務(wù)運(yùn)行的協(xié)程。
因此問題還是有很多的:
比如當(dāng)在協(xié)程里面調(diào)用棧超過2M時(shí),這個(gè)是需要處理的,現(xiàn)在的代碼是沒有做的,理應(yīng)中斷程序,避免寫壞堆,產(chǎn)生隨機(jī)的不可重現(xiàn)的問題。
顯然在實(shí)現(xiàn)時(shí)沒有考慮到多線程,如果在多線程環(huán)境里面運(yùn)行,需要代碼做同步處理。
現(xiàn)在的這個(gè)版本的協(xié)程有一個(gè)約定,在協(xié)程里調(diào)用的函數(shù)不能阻塞在syscall,這顯然也是不科學(xué)的。一個(gè)完整的協(xié)程庫,應(yīng)該包含一些常用的syscall的非阻塞的實(shí)現(xiàn),畢竟只有一個(gè)線程不能真的阻塞在這個(gè)調(diào)用上。
感謝各位的閱讀,以上就是“如何用C語言代碼實(shí)現(xiàn)協(xié)程”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對如何用C語言代碼實(shí)現(xiàn)協(xié)程這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!