Minicoro是單文件庫,用於在C中使用不對稱的Coroutines。API靈感來自Lua Coroutines,但考慮使用C。
該項目主要是成為Nelua編程語言的Coroutine後端。
圖書館大會實施受到Mike Pall的Lua Coco的啟發。
大多數平台通過不同的方法支持:
| 平台 | 裝配方法 | 後備方法 |
|---|---|---|
| 安卓 | 手臂/ARM64 | N/A。 |
| ios | 手臂/ARM64 | N/A。 |
| 視窗 | x86_64 | Windows纖維 |
| Linux | x86_64/i686 | UContext |
| Mac OS X | X86_64/ARM/ARM64 | UContext |
| WebAssembly | N/A。 | emscripten纖維 /二元異步 |
| 覆盆子pi | 手臂 | UContext |
| RISC-V | RV64/RV32 | UContext |
默認情況下,如果由編譯器和CPU支持,則使用彙編方法,否則UContext或Fiber方法被用作後備。
組裝方法非常有效,只需幾個週期才能創建,恢復,屈服或破壞Coroutine。
mco_coro對像不是線程安全的,您應該在多線程應用程序中使用MUTEX對其進行操縱。thread_local限定符的C編譯器進行編譯。thread_local ,編譯器可能會緩存線程局部變量指針,當Coroutine Switch線程時可能無效。-s ASYNCIFY=1進行編譯。Coroutine代表獨立的“綠色”執行線。但是,與多線程系統中的線程不同,Coroutine僅通過明確調用收益率函數來暫停其執行。
您可以通過調用mco_create創建Coroutine。它的唯一參數是一個mco_desc結構,其中描述了Coroutine。 mco_create功能僅創建一個新的Coroutine並返回手柄,它不會啟動Coroutine。
您通過調用mco_resume執行Coroutine。調用簡歷函數時,Coroutine通過調用其身體功能開始執行。 Coroutine開始運行後,它將運行直至終止或屈服。
通過致電mco_yield來產生Coroutine。當Coroutine產生收益率時,即使在嵌套功能呼叫中發生的收益率發生(即,而不是在主函數中),相應的簡歷也會立即返回。下次您恢復相同的Coroutine時,它從屈服的地步開始執行。
要將持久值與Coroutine相關聯,您可以選擇在其創建中設置user_data ,然後使用mco_get_user_data檢索。
要傳遞簡歷和收益率之間的值,您可以選擇使用mco_push和mco_pop API,它們旨在使用LIFO樣式緩衝區傳遞臨時值。存儲系統還可以用於在Coroutine創建或完成之前發送和接收初始值。
要使用Minicoro,請在一個.c文件中進行以下操作:
#define MINICORO_IMPL
#include "minicoro.h"您可以像其他任何標頭一樣在程序的其他部分中執行#include "minicoro.h" 。
以下簡單示例演示瞭如何使用庫:
#define MINICORO_IMPL
#include "minicoro.h"
#include <stdio.h>
#include <assert.h>
// Coroutine entry function.
void coro_entry ( mco_coro * co ) {
printf ( "coroutine 1n" );
mco_yield ( co );
printf ( "coroutine 2n" );
}
int main () {
// First initialize a `desc` object through `mco_desc_init`.
mco_desc desc = mco_desc_init ( coro_entry , 0 );
// Configure `desc` fields when needed (e.g. customize user_data or allocation functions).
desc . user_data = NULL ;
// Call `mco_create` with the output coroutine pointer and `desc` pointer.
mco_coro * co ;
mco_result res = mco_create ( & co , & desc );
assert ( res == MCO_SUCCESS );
// The coroutine should be now in suspended state.
assert ( mco_status ( co ) == MCO_SUSPENDED );
// Call `mco_resume` to start for the first time, switching to its context.
res = mco_resume ( co ); // Should print "coroutine 1".
assert ( res == MCO_SUCCESS );
// We get back from coroutine context in suspended state (because it's unfinished).
assert ( mco_status ( co ) == MCO_SUSPENDED );
// Call `mco_resume` to resume for a second time.
res = mco_resume ( co ); // Should print "coroutine 2".
assert ( res == MCO_SUCCESS );
// The coroutine finished and should be now dead.
assert ( mco_status ( co ) == MCO_DEAD );
// Call `mco_destroy` to destroy the coroutine.
res = mco_destroy ( co );
assert ( res == MCO_SUCCESS );
return 0 ;
}注意:如果您不想使用Minicoro分配系統,則應使用mco_desc.coro_size自己分配Coroutine對象並調用mco_init ,然後稍後銷毀呼叫mco_uninit並進行處理。
您可以從任何地方產生當前的運行coroutine,而不必將mco_coro指針傳遞給此,只需使用mco_yield(mco_running())即可。
該庫具有存儲接口,以協助產量和簡歷之間傳遞數據。它的用法很簡單,使用mco_push在mco_resume或mco_yield之前發送數據,然後在mco_resume或mco_yield之後使用mco_pop接收數據。注意不要將推動和彈出不匹配,否則這些功能將返回錯誤。
在濫用或系統錯誤的情況下,庫在大多數API中返回錯誤代碼,鼓勵用戶正確處理它們。
新的編譯時間選項MCO_USE_VMEM_ALLOCATOR啟用虛擬內存支持的分配器。
每個堆疊的Coroutine通常都必須為其完整的堆棧預留記憶,這通常會使數千個Coroutines的總記憶使用量非常高,例如,具有1000萬個Coroutine的應用程序,具有56KB的堆棧的應用程序將消耗高達5GB的存儲器,但是您的應用程序可能並不是每個Coroutine的全部堆棧使用情況。
由於這個問題,有些開發人員通常更喜歡無堆的旋ou旋場,而不是堆疊的珊瑚,而無堆的內存足跡很低,因此通常被認為更輕巧。但是,無匹配的局限性還有許多其他限制,例如您無法在其中運行不受限制的代碼。
解決方案的一種補救措施是使堆疊式的Coroutines可生長,只能在真正需要時按需使用物理內存,並且有一種很好的方法可以在操作系統支持時依賴虛擬內存分配。
虛擬內存支持的分配器將為每個Coroutine堆棧保留虛擬內存,但尚未觸發實際物理內存使用情況。儘管應用程序虛擬內存使用率很高,但物理內存使用率將很低,並且實際上按需增長(通常在Linux中的每個4KB塊)。
虛擬內存後支持的分配器還將默認堆棧大小提高到約2MB,通常是Linux中額外線程的大小,因此您的Coroutines中有更多的空間,並且堆棧溢出的風險很低。
例如,用近2MB堆棧保留的空間與虛擬內存分配器分配了783MB的物理內存使用情況,即每個Coroutine約為8KB,但是虛擬內存使用率為98GB。
建議僅當您打算在想要具有低內存足蹟的同時產生數千個Coroutines時才能啟用此選項。並非所有環境都具有具有虛擬內存支持的OS,因此默認情況下該選項被禁用。
此選項可能會在mco_create() / mco_destroy()中添加一個數量級開銷,因為它們會請求操作系統以管理虛擬內存頁面表,如果這是您的問題,請自定義自定義分配器以滿足您自己的需求。
可以定義以下以更改庫行為:
MCO_API公共API預選賽。默認值是extern 。MCO_MIN_STACK_SIZE創建Coroutine時的最小堆棧尺寸。默認值為32768(32KB)。MCO_DEFAULT_STORAGE_SIZE Coroutine存儲緩衝區的大小。默認值為1024。MCO_DEFAULT_STACK_SIZE創建Coroutine時默認堆棧大小。默認值為57344(56KB)。當MCO_USE_VMEM_ALLOCATOR為True時,默認值為2040KB(接近2MB)。MCO_ALLOC默認分配功能。默認值為calloc 。MCO_DEALLOC默認DealLocation函數。默認值是free 。MCO_USE_VMEM_ALLOCATOR使用虛擬內存支持的分配器,改善每個Coroutine的內存足跡。MCO_NO_DEFAULT_ALLOCATOR使用MCO_ALLOC和MCO_DEALLOC禁用默認分配器。MCO_ZERO_MEMORY堆棧存儲時堆棧的零內存,用於垃圾收集的環境。MCO_DEBUG啟用調試模式,記錄任何運行時錯誤以符合Stdout。除非定義NDEBUG或MCO_NO_DEBUG否則自動定義。MCO_NO_DEBUG禁用調試模式。MCO_NO_MULTITHREAD禁用多線程用法。當支持thread_local時,支持多線程。MCO_USE_ASM彙編上下文開關實現的強制使用。MCO_USE_UCONTEXT強制使用UContext上下文開關實現。MCO_USE_FIBERS強制使用纖維上下文開關實現。MCO_USE_ASYNCIFY強制使用二進制異步上下文開關實現。MCO_USE_VALGRIND定義是否要使用Valgrind運行以修復訪問內存錯誤。將Coroutine庫用於X86_64計數CPU週期的上下文開關(以簡歷或產量觸發)和初始化。
| CPU拱門 | 作業系統 | 方法 | 上下文開關 | 初始化 | 非初始化 |
|---|---|---|---|---|---|
| x86_64 | Linux | 集會 | 9個週期 | 31個週期 | 14個週期 |
| x86_64 | Linux | UContext | 352個週期 | 383個週期 | 14個週期 |
| x86_64 | 視窗 | 纖維 | 69個週期 | 10564週期 | 11167週期 |
| x86_64 | 視窗 | 集會 | 33個週期 | 74個週期 | 14個週期 |
注意:在Intel Core i7-8750h CPU @ 2.20GHz上進行了測試,並帶有預分配的coroutines。
這是所有庫函數的列表,供快速參考:
/* Structure used to initialize a coroutine. */
typedef struct mco_desc {
void ( * func )( mco_coro * co ); /* Entry point function for the coroutine. */
void * user_data ; /* Coroutine user data, can be get with `mco_get_user_data`. */
/* Custom allocation interface. */
void * ( * alloc_cb )( size_t size , void * allocator_data ); /* Custom allocation function. */
void ( * dealloc_cb )( void * ptr , size_t size , void * allocator_data ); /* Custom deallocation function. */
void * allocator_data ; /* User data pointer passed to `alloc`/`dealloc` allocation functions. */
size_t storage_size ; /* Coroutine storage size, to be used with the storage APIs. */
/* These must be initialized only through `mco_init_desc`. */
size_t coro_size ; /* Coroutine structure size. */
size_t stack_size ; /* Coroutine stack size. */
} mco_desc ;
/* Coroutine functions. */
mco_desc mco_desc_init ( void ( * func )( mco_coro * co ), size_t stack_size ); /* Initialize description of a coroutine. When stack size is 0 then MCO_DEFAULT_STACK_SIZE is used. */
mco_result mco_init ( mco_coro * co , mco_desc * desc ); /* Initialize the coroutine. */
mco_result mco_uninit ( mco_coro * co ); /* Uninitialize the coroutine, may fail if it's not dead or suspended. */
mco_result mco_create ( mco_coro * * out_co , mco_desc * desc ); /* Allocates and initializes a new coroutine. */
mco_result mco_destroy ( mco_coro * co ); /* Uninitialize and deallocate the coroutine, may fail if it's not dead or suspended. */
mco_result mco_resume ( mco_coro * co ); /* Starts or continues the execution of the coroutine. */
mco_result mco_yield ( mco_coro * co ); /* Suspends the execution of a coroutine. */
mco_state mco_status ( mco_coro * co ); /* Returns the status of the coroutine. */
void * mco_get_user_data ( mco_coro * co ); /* Get coroutine user data supplied on coroutine creation. */
/* Storage interface functions, used to pass values between yield and resume. */
mco_result mco_push ( mco_coro * co , const void * src , size_t len ); /* Push bytes to the coroutine storage. Use to send values between yield and resume. */
mco_result mco_pop ( mco_coro * co , void * dest , size_t len ); /* Pop bytes from the coroutine storage. Use to get values between yield and resume. */
mco_result mco_peek ( mco_coro * co , void * dest , size_t len ); /* Like `mco_pop` but it does not consumes the storage. */
size_t mco_get_bytes_stored ( mco_coro * co ); /* Get the available bytes that can be retrieved with a `mco_pop`. */
size_t mco_get_storage_size ( mco_coro * co ); /* Get the total storage size. */
/* Misc functions. */
mco_coro * mco_running ( void ); /* Returns the running coroutine for the current thread. */
const char * mco_result_description ( mco_result res ); /* Get the description of a result. */以下是一個更完整的示例,生成斐波那契數:
#define MINICORO_IMPL
#include "minicoro.h"
#include <stdio.h>
#include <stdlib.h>
static void fail ( const char * message , mco_result res ) {
printf ( "%s: %sn" , message , mco_result_description ( res ));
exit ( -1 );
}
static void fibonacci_coro ( mco_coro * co ) {
unsigned long m = 1 ;
unsigned long n = 1 ;
/* Retrieve max value. */
unsigned long max ;
mco_result res = mco_pop ( co , & max , sizeof ( max ));
if ( res != MCO_SUCCESS )
fail ( "Failed to retrieve coroutine storage" , res );
while ( 1 ) {
/* Yield the next Fibonacci number. */
mco_push ( co , & m , sizeof ( m ));
res = mco_yield ( co );
if ( res != MCO_SUCCESS )
fail ( "Failed to yield coroutine" , res );
unsigned long tmp = m + n ;
m = n ;
n = tmp ;
if ( m >= max )
break ;
}
/* Yield the last Fibonacci number. */
mco_push ( co , & m , sizeof ( m ));
}
int main () {
/* Create the coroutine. */
mco_coro * co ;
mco_desc desc = mco_desc_init ( fibonacci_coro , 0 );
mco_result res = mco_create ( & co , & desc );
if ( res != MCO_SUCCESS )
fail ( "Failed to create coroutine" , res );
/* Set storage. */
unsigned long max = 1000000000 ;
mco_push ( co , & max , sizeof ( max ));
int counter = 1 ;
while ( mco_status ( co ) == MCO_SUSPENDED ) {
/* Resume the coroutine. */
res = mco_resume ( co );
if ( res != MCO_SUCCESS )
fail ( "Failed to resume coroutine" , res );
/* Retrieve storage set in last coroutine yield. */
unsigned long ret = 0 ;
res = mco_pop ( co , & ret , sizeof ( ret ));
if ( res != MCO_SUCCESS )
fail ( "Failed to retrieve coroutine storage" , res );
printf ( "fib %d = %lun" , counter , ret );
counter = counter + 1 ;
}
/* Destroy the coroutine. */
res = mco_destroy ( co );
if ( res != MCO_SUCCESS )
fail ( "Failed to destroy coroutine" , res );
return 0 ;
}MCO_USE_VMEM_ALLOCATOR選項,用於分配數千個具有低內存足蹟的Coroutines,這包括打破分配器API的變化。我是一名全日制開源開發人員,通過我的Github進行的任何捐贈都將受到讚賞,並可以鼓勵我繼續支持該和其他開源項目。我可能會接受一次性贊助,以供小型功能或與項目目標保持一致的次要增強功能,在這種情況下,請與我聯繫。
您選擇的公共領域或MIT無歸因,請參閱許可證文件。