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

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

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

3天內不再提示

何選擇一個合適的協程來獲得CPU執行權

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

如今雖不敢說協程已經是紅的發紫,但確實是越來越受到了大家的重視。Golang中的已經是只有goroutine,以至于很多go程序員是只知有協程,不知有線程了。就連C++也在最新的C++20中原生支持協程。更不用說很多活躍的語言如pythonjava等,也都是支持協程的。盡管這些協程可能名稱不同,甚至用法也不同,但它們都可以被劃分為兩大類,一類是有(stackful) 協程,例如 goroutine,libco;一類是無棧 (stackless) 協程,例如C++的協程。

這里我們想說的一點是所謂的有棧,無棧并不是說這個協程運行的時候有沒有棧,而是說協程之間是否存在調用棧(callbackStack)。其實仔細一想即可,但凡是個正在運行的程序,不管你是協程也好,線程也好,怎么可能在運行的時候不使用棧空間呢,調用參數往哪擱,局部變量往哪擱。我們知道基本所有的主流語言在調用另外一個函數的時候都存在一個調用棧,我們來解釋一下調用棧這個詞:

這幅圖是有兩個棧幀的調用棧,我在這篇文章中對棧幀下過定義,即:函數的棧幀是指esp和ebp之間的一塊地址。拿上圖來說ebp存儲著Frame Pointer指向的地址,Return Address當然就是我們在執行完最新的棧幀以后下一步要執行的指令地址。esp當然就是當前指向棧頂的指針了。

有棧協程

很多地方又把協程稱為subroutine,subroutine是什么,就是函數。上古時期的計算機科學家們早就給出了概念,coroutine就是可以中斷并恢復執行的subroutine,從這個角度來看協程擁有調用棧并不是一個奇怪的事情。我們再來思考coroutine與subroutinue相比有什么區別,你會發現區別僅有一個,就是coroutinue可以中斷并恢復,對應的操作就是yield/resume,這樣看來subroutinue不過是coroutinue的一個子集罷了。也就是說把協程當做一個特殊的函數調用,有棧協程就是我們理想中協程該有的模樣。

既然把其當做一個特殊的函數調用,對我們來說最嚴峻的挑戰就是如何像切換函數一樣去切換協程,難點在于除了像函數一樣切換出去,還要在某種條件滿足的時候切換回來,我們的做法可以是在協程內部存儲自身的上下文,并在需要切換的時候把上下文切換就可以了,我們知道上下文其實本質上就是寄存器,所以保存上下文實際上就是把寄存器的值保存下來,有兩種方法,一種是使用匯編,libco就使用了這種方法。還有一種是使用ucontext.h,這個封裝好的庫也可以幫我們完成相關工作。

匯編的話我們來看一看libco中對于32位機器的上下文切換操作是如何完成的:

// 獲取第一個參數
    movl 4(%esp), %eax 
    // 參數的類型我們暫且理解為一個擁有八個指針的數組,即regs
	| regs[7] |
	| regs[6] |
	| regs[5] |
	| regs[4] |
	| regs[3] |
	| regs[2] |
	| regs[1] |
	| regs[0] |
	--------------   < ---EAX

    movl %esp,  28(%eax)  
    movl %ebp, 24(%eax)
    movl %esi, 20(%eax)
    movl %edi, 16(%eax)
    movl %edx, 12(%eax)
    movl %ecx, 8(%eax)
    movl %ebx, 4(%eax)
	// 想想看,這里eax加偏移不就是對應了regs中的值嗎?這樣就把所有寄存器中的值保存在了參數中

	
	// ESP偏移八位就是第二個參數的偏移了,這樣我們就可以把第二個參數regs中的上下文切換到寄存器中了
    movl 8(%esp), %eax 
    movl 4(%eax), %ebx
    movl 8(%eax), %ecx
    movl 12(%eax), %edx  
    movl 16(%eax), %edi
    movl 20(%eax), %esi
    movl 24(%eax), %ebp
    movl 28(%eax), %esp

	ret
	// 這樣我們就完成了一次協程的切換

我們可以看到其實就是參數中傳入兩個協程的上下文結構,然后第一個參數執行保存上下文,然后把第二個參數的上下文存入寄存器,這樣就執行了兩個協程的切換。

當然我們上面提到了調用棧,那么既然有調用棧,那么肯定有一個執行的順序,即一定要把棧頂的協程全部運行完才可以運行下一層的協程,這樣說可能比較抽象,我們舉一個簡單的例子:

主協程A中執行協程B,此時調用棧是在[A,B]和[A]之間切換,因為B會主動讓出執行權,然后調用棧上此時就只有一個A了

B協程中執行C,D協程,此時調用棧是在[A,B,C],[A,B],[A,B,D]之間轉換的,

這樣看來我們總是只能在調用棧頂的協程運行完以后才能去執行更低一層的協程,當然,這也是典型的非對稱協程,即協程之間有明顯的調用關系。

當然在我的描述中也可以看出有棧協程涉及到對于寄存器的保存和修改,也涉及到對每一個協程棧(實際運行的棧)的分配。對于寄存器來說,現代寄存器基本都是上百個字節的數據,還有每一個協程的棧,如果選擇了共享棧,又涉及到對棧上數據的拷貝,顯然在效率上來說相比無棧協程的確是有一些損失的。

無棧協程

那么所謂的無棧協程是什么呢?其實無棧協程的本質就是一個狀態機(state machine),它可以理解為在另一個角度去看問題,即同一協程協程的切換本質不過是指令指針寄存器的改變。這里推薦一篇文章,其內容是用C語言實現一個協程,其實就是一個無棧協程的實現。

我們來看一個使用libco的協程的例子,當然libco是一個有棧協程:

void* test(void* para){
	co_enable_hook_sys();
	int i = 0;
	poll(0, 0, 0. 1000); // 協程切換執行權,1000ms后返回
	i++;
	poll(0, 0, 0. 1000); // 協程切換執行權,1000ms后返回
	i--;
	return 0;
}

int main(){
	stCoRoutine_t* routine;
	co_create(&routine, NULL, test, 0);// 創建一個協程
	co_resume(routine); 
	co_eventloop( co_get_epoll_ct(),0,0 );
	return 0;
}

這段代碼實際的意義就是主協程跑一個協程去執行test函數,在test中我們需要兩次從協程中切換出去,這里對應了兩個poll操作(hook機制,有興趣的朋友可以點擊這里),hook后的poll所做的事情就是把當前協程的CPU執行權切換到調用棧的上一層,并在超時或注冊的fd就緒時返回(當然樣例這里就只是超時了)。那么無棧協程跑相同的代碼是怎么樣的呢?其實就是翻譯成類似于以下代碼:

struct test_coroutine {
    int i;
    int __state = 0;
    void MoveNext() {
        switch(__state) {
        case 0:
            return frist();
        case 1:
            return second();
        case 2:
        	return third();
        }
    }
    void frist() {
        i = 0;
        __state = 1;
    }
    void second() {
        i++;
        _state = 2;
    }
    void third() {
    	i--;
    }
};

我們可以看到相比與有棧協程中的test函數,這里把整個協程抽象成一個類,以原本需要執行切換的語句處為界限,把函數劃分為幾個部分,并在某一個部分執行完以后進行狀態轉移,在下一次調用此函數的時候就會執行下一部分,這樣的話我們就完全沒有必要像有棧協程那樣顯式的執行上下文切換了,我們只需要一個簡易的調度器來調度這些函數即可。

從執行時棧的角度來看,其實所有的協程共用的都是一個棧,即系統棧,也就也不必我們自行去給協程分配棧,因為是函數調用,我們當然也不必去顯示的保存寄存器的值,而且相比有棧協程把局部變量放在新開的空間上,無棧協程直接使用系統棧使得CPU cache局部性更好,同時也使得無棧協程的中斷和函數返回幾乎沒有區別,這樣也可以凸顯出無棧協程的高效。

對稱協程與非對稱協程

其實對于“對稱”這個名詞,闡述的實際是協程之間的關系,用大白話來說就是對稱協程就是說協程之間人人平等,沒有誰調用誰一說,大家都是一樣的,而非對稱協程就是協程之間存在明顯的調用關系。

簡單來說就是這樣:

  • 對稱協程 Symmetric Coroutine:任何一個協程都是相互獨立且平等的,調度權可以在任意協程之間轉移。
  • 非對稱協程 Asymmetric Coroutine:協程出讓調度權的目標只能是它的調用者,即協程之間存在調用和被調用關系。

其實兩者的實現我覺得其實差異不大,非對稱協程其實就是擁有調用棧,而非對稱協程則是大家都平等,不需要調用棧,只需要一個數據結構存儲所有未執行完的協程即可。至于哪種更優?我覺得分情況,如果你使用協程的目的是為了優化一些IO密集型應用,那么協程切換出去的時候就是它等待事件到來的時候,此時你就算切換過去也沒有什么意義,還不如等到事件到來的時候自動切換回去。

其實上面說的是有一些問題,因為這個執行權的切換實際上是(調用者–被調用者)之間的切換,對稱就是它們之間都是平等的,就是假如A協程執行了B,C協程,那么B協程可以切換回A,也可以切換回C。而非對稱只能是B切換回A,A切換回C,C再切換回A,以此類推。

這樣看起來顯然非對稱協程相比之下更為符合我們的認知,因為對稱協程目前我不知道如何選擇一個合適的協程來獲得CPU執行權,正如上面所說,此協程可能正在等待事件。當然如果調度算法足夠優秀的話,對稱協程也是可取的。

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

    關注

    68

    文章

    10855

    瀏覽量

    211594
  • 程序
    +關注

    關注

    117

    文章

    3785

    瀏覽量

    81005
  • 函數
    +關注

    關注

    3

    文章

    4327

    瀏覽量

    62573
  • C++
    C++
    +關注

    關注

    22

    文章

    2108

    瀏覽量

    73623
收藏 人收藏

    評論

    相關推薦

    談談的那些事兒

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

    和線程有什么區別

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

    Python中的多核CPU共享數據之詳解

    又稱微線程,coroutne,種用戶態的輕量級線程。通俗點講就是周末我在家里休息,假如我先洗漱,再煮飯,再下載電影看會很慢,用了
    的頭像 發表于 12-07 10:23 ?6620次閱讀
    Python中的多核<b class='flag-5'>CPU</b>共享數據之<b class='flag-5'>協</b><b class='flag-5'>程</b>詳解

    Python自動化運維之函數賦值過程

    及同步的開銷(3)方便切換控制流,簡化編程模型(4)高并發+高擴展性+低成本:CPU支持上萬的都不是問題。所以很適合用于高并發處理。
    的頭像 發表于 03-18 11:22 ?3728次閱讀

    關于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(在 Python 中的實現)修飾的代碼,我頓時
    的頭像 發表于 09-23 14:38 ?1326次閱讀

    Python與JavaScript的對比及經驗技巧

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

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

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

    的概念及的掛起函數介紹

    種輕量級的線程,它可以在單個線程中實現并發執行。與線程不同,不需要操作系統的上下文切
    的頭像 發表于 04-19 10:20 ?887次閱讀

    Kotlin實戰進階之筑基篇1

    。 Android 中的每個應用都會運行主線程,它主要是用來處理 UI,如果主線程上需要處理的任務太多,應用就感覺被卡主樣影響用戶體驗,得讓那些耗時的任務不阻塞主線程的運行。要做到處理網絡請求不會阻塞主線程,
    的頭像 發表于 05-30 16:24 ?711次閱讀
    Kotlin<b class='flag-5'>協</b><b class='flag-5'>程</b>實戰進階之筑基篇1

    Kotlin實戰進階之筑基篇3

    。 Android 中的每個應用都會運行主線程,它主要是用來處理 UI,如果主線程上需要處理的任務太多,應用就感覺被卡主樣影響用戶體驗,得讓那些耗時的任務不阻塞主線程的運行。要做到處理網絡請求不會阻塞主線程,
    的頭像 發表于 05-30 16:26 ?695次閱讀

    FreeRTOS任務與介紹

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

    的作用、結構及原理

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

    C/C++編程的相關概念和技巧

    、引言 的定義和背景 (Coroutine),又稱為微線程或者輕量級線程,是種用戶態
    的頭像 發表于 11-09 11:34 ?774次閱讀

    的實現與原理

    前言 這個概念很久了,好多程序員是實現過這個組件的,網上關于的文章,博客,論壇都是汗牛充棟,在知乎,github上面也有很多大牛寫了關于
    的頭像 發表于 11-10 10:57 ?432次閱讀
    主站蜘蛛池模板: 久久全国免费观看视频| 1V1各种PLAY女主被肉| 国产爱豆剧果冻传媒在线| 午夜小视频免费观看| 国产专区青青在线视频| 亚洲二区电影| 女人张腿让男人桶免费| 国产99久久久国产精品成人| 亚洲.日韩.欧美另类| 牛牛在线视频| 精品久久久久久久99热| 97视频在线免费| 亚洲精品无码久久久久A片空| 美女内射视频WWW网站午夜 | www.欧美与曽交性video| 99re久久热在线播放快| 亚洲日本欧美产综合在线| 在线观看日本免费| gogo亚洲肉体艺术照片9090| 国产精品九九九久久九九| 九九99热久久999精品| 暖暖 免费 日本 高清 在线1| 日本精品久久久久中文字幕| 丫鬟粗大狠狠贯穿h| 88福利视频| 欧美末成年videos丨| 十九岁韩国电影在线观看| 影音先锋男人资源813.| 扒开胸罩揉她的乳尖视频| 好男人在线高清WWW免费观看| 欧美xxxx性喷潮| 亚洲欧美精品无码一区二在线| 99C视频色欲在线| 国产午夜精品福利久久| 男人边吃奶边摸边做刺激情话| 我们中文在线观看免费完整版| 在线观看黄色小说| 国产精品VIDEOS麻豆TUBE| 快播看av| 蜜桃麻豆WWW久久囤产精品免费| 欧美美女一区二区三区|