Minicoro-это библиотека с одним файлом для использования асимметричных связок в C. API вдохновлен Lua Coroutines, но с учетом C.
Проект разрабатывается главным образом как бэкэнд для языка программирования Nelua.
Реализация библиотечной сборки вдохновлена Lua Coco от Mike Pall.
Большинство платформ поддерживаются различными методами:
| Платформа | Метод сборки | Метод резервы |
|---|---|---|
| Android | ARM/ARM64 | N/a |
| ios | ARM/ARM64 | N/a |
| Окна | x86_64 | Волокна Windows |
| Linux | x86_64/i686 | UContext |
| Mac OS X. | x86_64/arm/arm64 | UContext |
| Webassembly | N/a | Emscripten волокна / бинарный асинцифицируют |
| Raspberry Pi | РУКА | UContext |
| RISC-V | RV64/RV32 | UContext |
Метод сборки используется по умолчанию, если он поддерживается компилятором и процессором, в качестве отступления используется Ucontext или Fiber Method.
Метод сборки очень эффективен, для создания, возобновления, возобновления или уничтожения коратики требуется несколько циклов.
mco_coro не является безопасным потоком, вы должны использовать Mutex для манипулирования его в приложениях с многопоточной.thread_local .thread_local внутри кода COROUTINE, компилятор может кэшировать потоки локальных переменных, которые могут быть недействительными, когда резьбы переключателя COROUTINE.-s ASYNCIFY=1 .Коратика представляет собой независимый «зеленый» поток исполнения. Однако в отличие от потоков в многопоточных системах, коратика только приостанавливает ее выполнение, явно вызывая функцию урожайности.
Вы создаете Coroutine, вызывая mco_create . Его единственным аргументом является структура mco_desc с описанием для Coroutine. Функция mco_create создает только новую корутину и возвращает ручку, она не запускает Coroutine.
Вы выполняете Coroutine, позвонив mco_resume . При вызове функции резюме Coroutine начинает выполнение, вызывая функцию ее тела. После того, как коратика начнет работать, она работает до тех пор, пока не завершится или не допускается.
Корутика дает вызов mco_yield . Когда коратика даст, соответствующее резюме возвращается немедленно, даже если выход происходит внутри вложенных вызовов функций (то есть не в основной функции). В следующий раз, когда вы возобновите ту же кораку, он продолжает выполнять свою исполнение с точки зрения, где он уступил.
Чтобы связать постоянное значение с Coroutine, вы можете при желании установить user_data на его создание, а затем получить с помощью mco_get_user_data .
Чтобы пройти значения между резюме и урожайностью, вы можете при желании использовать API mco_push и mco_pop , они предназначены для прохождения временных значений с использованием буфера в стиле 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, вы должны выделить объект Coroutine самостоятельно, используя mco_desc.coro_size и позвонить mco_init , а затем уничтожить CALL mco_uninit и рассказать об этом.
Вы можете дать текущую бегущую кораку из никуда, не проходившей поставки mco_coro , чтобы это просто использовать mco_yield(mco_running()) .
Библиотека имеет интерфейс хранения, чтобы помочь передачи данных между выходом и резюме. Это использование является простым, используйте mco_push для отправки данных перед mco_resume или mco_yield , а затем используйте mco_pop после mco_resume или mco_yield для получения данных. Позаботьтесь о том, чтобы не соответствовать толчке и всплеску, иначе эти функции вернут ошибку.
Библиотека возвращает коды ошибок в большинстве своих API В случае неправильного использования или системной ошибки пользователю рекомендуется правильно обрабатывать их.
Новый параметр «Время компиляции» MCO_USE_VMEM_ALLOCATOR позволяет выдвинуть вариант виртуальной памяти.
Каждая стойкая коратика, как правило, должна зарезервировать память для полного стека, это обычно делает общее использование памяти очень высоким при распределении тысяч коратиков, например, приложение с 100 тысячами коратики со стеком 56 КБ будет потреблять столько 5 ГБ памяти, однако ваше приложение может не полное использование стека для каждой обруки.
Некоторые разработчики часто предпочитают без стека -коратики по сравнению с стеклянными коратиками из -за этой проблемы, без стека следов памяти низкий, поэтому часто считается более легким. Тем не менее, Sackless имеет много других ограничений, как вы не можете запустить неограниченный код внутри них.
Одно лекарство от решения состоит в том, чтобы сделать стекнувшие кругии, чтобы использовать физическую память только по требованию, когда она действительно необходима, и есть хороший способ сделать это полагаться на распределение виртуальной памяти при поддержке операционной системы.
Обоснованная виртуальная память будет резервировать виртуальную память в ОС для каждого стека Coroutine, но пока не запускает реальное использование физической памяти. В то время как использование виртуальной памяти приложения будет высоким, использование физической памяти будет низким и фактически будет расти по требованию (обычно каждые 4 КБ в Linux).
Обоснованная виртуальная память также повышает размер стека по умолчанию примерно до 2 МБ, как правило, размер дополнительных потоков в Linux, так что у вас есть больше места в ваших коратиках, и риск переполнения стека низкий.
В качестве примера, выделение 100 тысяч коратиков с зарезервированным пространством стека почти 2 МБ с распределителем виртуальной памяти используется 783 МБ использования физической памяти, то есть около 8 КБ на Coroutine, однако использование виртуальной памяти будет на 98 ГБ.
Рекомендуется включить эту опцию только в том случае, если вы планируете породить тысячи коратиков, в то же время хотите получить низкую площадь памяти. Не все среды имеют ОС с поддержкой виртуальной памяти, поэтому эта опция отключена по умолчанию.
Эта опция может добавить порядок накладных расходов на mco_create() / mco_destroy() , поскольку они будут просить ОС управлять таблицами страниц виртуальной памяти, если это проблема для вас, пожалуйста, настройте пользовательский выделений для ваших собственных потребностей.
Следующее можно определить, чтобы изменить поведение библиотеки:
MCO_API - Общественный квалификатор API. По умолчанию extern .MCO_MIN_STACK_SIZE - Минимальный размер стека при создании COROUTINE. По умолчанию 32768 (32 КБ).MCO_DEFAULT_STORAGE_SIZE - размер буфера хранения Coroutine. По умолчанию 1024.MCO_DEFAULT_STACK_SIZE - размер стека по умолчанию при создании коратики. По умолчанию 57344 (56 КБ). Когда MCO_USE_VMEM_ALLOCATOR TRUE, по умолчанию составляет 2040 КБ (почти 2 МБ).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 - Отключить использование многопоточного. MultiThread поддерживается, когда поддерживается thread_local .MCO_USE_ASM - Принудительное использование реализации переключения контекста сборки.MCO_USE_UCONTEXT - Принудительное использование реализации переключения контекста UCEXT.MCO_USE_FIBERS - Использование Внедрения контекстного переключения волокон. Реализация переключения контекста.MCO_USE_ASYNCIFY - Использование принудительного использования BINARYEN Asyncify Context Switch реализация.MCO_USE_VALGRIND - Определите, хотите ли вы запустить с Valgrind, чтобы исправить доступ к ошибкам памяти.Библиотека Coroutine была оценена для цикла ЦП X86_64 для переключателя контекста (запускаемое в резюме или урожайности) и инициализации.
| ЦП арка | ОС | Метод | Контекст переключатель | Инициализировать | Неонициализировать |
|---|---|---|---|---|---|
| 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 при 2,20 ГГц с предварительно выделенными коратиками.
Вот список всех библиотечных функций для быстрой ссылки:
/* 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. */Ниже приведен более полный пример, генерируя числа Fibonacci:
#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 для выделения тысяч коратиков с низкой площадью памяти, это включает в себя нарушение изменений в API распределения.Я работающий на полный рабочий день с открытым исходным кодом, любое количество пожертвований через мой GitHub будет оценен и может привести меня в поддержку поддержать эти и другие проекты с открытым исходным кодом. Я могу принять одноразовое спонсорство для небольших функций или незначительных улучшений, связанных с целями проекта, в данном случае свяжитесь со мной.
Ваш выбор общественного домена или MIT No Attribution см. В файл лицензии.