仿製雲風的協程庫的接口設計,我花了一個下午加晚上的時間重構了以前寫的協程庫,提供的接口如今和雲風大大的協程接口如出一轍,都是仿製lua的非對稱協程。咱們依舊沒有用ucontext.h組件(由於ucontext.h組件在osX下已經deprecated了,若是你加入sys/ucontext.h頭文件,發現某些小型C協程庫還能運行,純屬巧合,由於官方已經不維護這個組件了,之後極可能出錯),咱們的協程庫能夠運行在兼容X86平臺的操做系統上,各類unix-like操做系統,windows操做系統均可以,不過得用gcc或者clang或者與之兼容的mingw編譯工具編譯出32位的程序運行,不能用vs或者vc++系列,由於咱們用了gasm內聯彙編格式,固然你能夠稍微改下幾行彙編就能移植到vs或者vc++版本。在多線程環境下運行協程時,不一樣線程不能共享協程組,也就是說任意兩個協程,若它們屬於不一樣的線程,那麼它們得屬於不一樣的協程組。這是基本編程準則。好比想在POSIX多線程接口裏頭用咱們的協程庫時候,你能夠這麼用:前端
1 #include <all needed>
2
3 hello(schedular *s)
4 { 5 coroutine_new(s, otherfunc, args); /* 在s協程組裏頭建立一個協程, 這個s多是S或S1等等 */ 6 ... do something 7 } 8 9 foo(...) 10 { 11 schedular *S1 =coroutine_open(); /* 分配一個協程組S1,這個只能屬於線程tid */ 12 int co = coroutine_new(S1, hello, S1); /* 建立一個協程 */ 13 ... do something 14 coroutine_resume(S1,co); /* 調用本地協程組裏頭的co協程 */ 15 ... do something 16 coroutine_close(&S1); /* 釋放S1協程組 */ 17 } 18 19 main{ 20 schedular *S =coroutine_open(); /* 分配一個協程組S,這個只能屬於主線程 */ 21 pthread_create(tid, ..., foo, ...); /* 建立Posix線程 */ 22 int co = coroutine_new(S, hello, S); /* 建立一個協程 */ 23 ... do something 24 coroutine_resume(S,co); /* 調用本地協程組裏頭的co協程 */ 25 ... do something 26 coroutine_close(&S); /* 釋放S協程組 */ 27 pthread_join(tid, ...); /* 等待並回收線程資源 */ 28 }
若是要想不一樣線程間的協程通信,得用操做系統各自的API,好比用共享內存方式來實現,咱們沒有實現相似goroutine的channel。協程庫爲共享棧模式,一個協程組能夠容納最多一百萬個協程,每一個協程共用128Kbytes棧空間,我用top命令監測了一下運行一百萬個協程的測試程序,此時該測試程序內存佔用峯值爲280M左右,能夠推算每一個協程內存佔用峯值爲280bytes左右。能夠推斷,若是運行一千萬個協程,咱們至少須要10個協程組,每一個協程組280M,一共2800M = 2.7G左右 = 4G總理論空間 - 1G內核空間,因此咱們的協程庫所能支持的最大協程數是1000萬,這是理論上限。用gprof測試程序性能得知,2999997次協程切換共用0.39秒,每次切換時間在130ns左右。最重要的是,咱們的協程庫全部的源碼加起來大概只有400行左右,這還包括了微量的註釋和頭文件。若是可以移植到X64版本,那麼咱們的協程庫能夠輕鬆支持千萬數量的協程,能夠在實際開發中使用,再也不是單單隻有教學意義的小玩意了。惋惜的是在unix like的各類平臺下,做爲惟一的異步非阻塞IO組件,linux kernal實現的aio組件並非那麼成熟(可能要爛尾了),並且glibc中用線程+信號模擬的用戶態aio組件也是有不少bug存在。異步操做與協程的結合果真仍是在語言層面(作編譯器前端)實現更好,而非在庫上實現。linux
項目GitHub連接:https://github.com/Yuandong-Chen/coroutine/tree/ezco.v.0.0.1 c++
後記:git
最近用setjmp.h組件,重構了項目,刪去了彙編代碼,把項目移植到了X64-macosx-clang版本(只兼容intel X64的處理器,osX操做系統以及Clang編譯器,不兼容其餘任何變化,包括Linux,GCC,X86等等),能夠支持上億個協程(思考下?爲什麼?)。項目放在上面GitHub連接裏頭的默認版本內。到此爲止,咱們的協程完成度近似libconcurrency庫。如何移植到X64-linux-gcc版本是一個問題,由於glibc裏頭,咱們沒法在jmp_buf數組中經過偏移量取出rip邏輯寄存器的取值。因此爲了達到可移植性,咱們仍是得用匯編寫一個本身的setjmp/longjmp函數,其實不少協程庫就是本身重寫了setjmp/longjmp以知足兼容性。這樣也有個壞處,那就是咱們得寫大量的彙編,對不一樣的C編譯器,不一樣的操做系統,不一樣的CPU都得寫一個對應的彙編版本,固然,能夠經過內聯彙編的方式在必定程度上減輕一點點工做量。這種兼容性的體力活我就不去幹了。github
這裏給出爲什麼能支持上億協程的答案:很簡單,咱們以1000萬個協程佔4G空間估計,那麼64位機若是有128G內存的話,1000萬*128/4 = 3億左右的協程併發。固然,我想沒人會用3億協程併發,由於即使是8核16線程,負載爲300000000/(16) = 1.8億協程/線程,除非你有超級計算機,那麼才能夠作到幾百協程/線程。macos
進一步須要作的:編程
1)完全放棄共享棧,每一個協程從新擁有本身獨立的棧空間。由於x64下的虛擬內存足夠大了,共享棧帶來的優點過小,咱們根本不須要幾億個協程併發,可是咱們須要他們執行的足夠快。windows
2)放棄lua的resume-yield協程模型,改用erlang的spawn模型,從而可以利用多核帶來的並行執行的優點。數組
3)添加協程間的channel機制(一種消息傳遞機制)。多線程
總的說來,咱們至關於要把erlang的輕量級進程這部分作成C語言庫。