Minicoro เป็นห้องสมุดไฟล์เดียวสำหรับใช้ coroutines แบบอสมมาตรใน C. API ได้รับแรงบันดาลใจจาก Lua coroutines แต่มีการใช้ C ในใจ
โครงการส่วนใหญ่ได้รับการพัฒนาให้เป็นแบ็กเอนด์ coroutine สำหรับภาษาการเขียนโปรแกรม Nelua
การดำเนินการประกอบห้องสมุดได้รับแรงบันดาลใจจาก Lua Coco โดย Mike Pall
แพลตฟอร์มส่วนใหญ่ได้รับการสนับสนุนด้วยวิธีการต่าง ๆ :
| แพลตฟอร์ม | วิธีการประกอบ | วิธีทางเลือก |
|---|---|---|
| Android | ARM/ARM64 | N/A |
| iOS | ARM/ARM64 | N/A |
| หน้าต่าง | x86_64 | เส้นใย windows |
| ลินเวกซ์ | x86_64/i686 | UContext |
| mac os x | x86_64/arm/arm64 | UContext |
| การใช้เว็บ | N/A | เส้นใย emscripten / binaryen asyncify |
| Raspberry Pi | แขน | UContext |
| RISC-V | RV64/RV32 | UContext |
วิธีการประกอบถูกใช้โดยค่าเริ่มต้นหากรองรับโดยคอมไพเลอร์และ CPU มิฉะนั้นจะใช้ UContext หรือวิธีไฟเบอร์เป็นทางเลือก
วิธีการประกอบมีประสิทธิภาพมากมันใช้เวลาเพียงไม่กี่รอบในการสร้างกลับมาทำงานหรือทำลาย coroutine
mco_coro ไม่ปลอดภัยคุณควรใช้ mutex สำหรับการจัดการในแอปพลิเคชันมัลติเธรดthread_localthread_local ภายในรหัส coroutine คอมไพเลอร์อาจแคชเธรดพอยน์เตอร์ตัวแปรโลคัลซึ่งสามารถไม่ถูกต้องเมื่อเกลียวสวิตช์ coroutine-s ASYNCIFY=1coroutine แสดงถึงหัวข้อการดำเนินการ "สีเขียว" ที่เป็นอิสระ แตกต่างจากเธรดในระบบมัลติเธรดอย่างไรก็ตาม coroutine เท่านั้นที่ระงับการดำเนินการของมันโดยเรียกฟังก์ชันผลผลิตอย่างชัดเจน
คุณสร้าง coroutine โดยเรียก mco_create ข้อโต้แย้งเพียงอย่างเดียวคือโครงสร้าง mco_desc พร้อมคำอธิบายสำหรับ coroutine ฟังก์ชั่น mco_create จะสร้าง coroutine ใหม่เท่านั้นและส่งคืนด้ามจับมันไม่ได้เริ่มต้น coroutine
คุณดำเนินการ coroutine โดยเรียก mco_resume เมื่อเรียกใช้ฟังก์ชั่นเรซูเม่ Coroutine จะเริ่มดำเนินการโดยเรียกใช้ฟังก์ชันร่างกาย หลังจาก coroutine เริ่มทำงานมันจะทำงานจนกว่าจะสิ้นสุดหรือให้ผลผลิต
Coroutine ให้ผลโดยเรียก mco_yield เมื่อ coroutine ให้ผลตอบแทนที่สอดคล้องกันจะกลับมาทันทีแม้ว่าผลผลิตจะเกิดขึ้นภายในการเรียกฟังก์ชั่นซ้อนกัน (นั่นคือไม่ใช่ในฟังก์ชันหลัก) ครั้งต่อไปที่คุณกลับมาทำงานร่วมกันต่อไปมันจะดำเนินการต่อจากจุดที่มันให้
ในการเชื่อมโยงค่าถาวรกับ coroutine คุณสามารถเลือก user_data ในการสร้างและเรียกคืนในภายหลังด้วย mco_get_user_data
ในการส่งผ่านค่าระหว่างเรซูเม่และผลผลิตคุณสามารถเลือกใช้ mco_push และ mco_pop APIs โดยมีจุดประสงค์เพื่อส่งค่าชั่วคราวโดยใช้บัฟเฟอร์สไตล์ 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 จากนั้นก็ทำลายการโทร mco_uninit และจัดการมัน
คุณสามารถให้ coroutine ที่วิ่งปัจจุบันได้จากทุกที่โดยไม่ต้องผ่านพอยน์เตอร์ mco_coro ไปรอบ ๆ เพื่อใช้ mco_yield(mco_running())
ห้องสมุดมีอินเทอร์เฟซที่เก็บข้อมูลเพื่อช่วยผ่านข้อมูลระหว่างผลผลิตและประวัติย่อ การใช้งานนั้นตรงไปตรงมาใช้ mco_push เพื่อส่งข้อมูลก่อน mco_resume หรือ mco_yield จากนั้นใช้ mco_pop ในภายหลังหลังจาก mco_resume หรือ mco_yield เพื่อรับข้อมูล ระวังอย่าให้การกดและป๊อปไม่ตรงกันมิฉะนั้นฟังก์ชั่นเหล่านี้จะส่งคืนข้อผิดพลาด
รหัสข้อผิดพลาดในการส่งคืนไลบรารีใน API ส่วนใหญ่ในกรณีที่มีข้อผิดพลาดในทางที่ผิดหรือข้อผิดพลาดของระบบผู้ใช้จะได้รับการสนับสนุนให้จัดการอย่างถูกต้อง
ตัวเลือกการรวบรวมเวลาใหม่ MCO_USE_VMEM_ALLOCATOR เปิดใช้งานหน่วยความจำเสมือนจริงสำรอง
coroutine แบบสแต็คทุกครั้งมักจะต้องสำรองหน่วยความจำสำหรับสแต็คเต็มรูปแบบซึ่งโดยทั่วไปจะทำให้การใช้งานหน่วยความจำทั้งหมดสูงมากเมื่อจัดสรร coroutines หลายพัน coroutines เช่นแอปพลิเคชันที่มี coroutine 100 พัน coroutine ที่มีสแต็ค 56KB จะใช้เวลาสูงถึง 5GB ของหน่วยความจำ
นักพัฒนาบางคนมักจะชอบ coroutines ที่ไร้สแต็คที่มากกว่า coroutines stackful เนื่องจากปัญหานี้รอยเท้าหน่วยความจำที่ไร้สแต็คนั้นต่ำดังนั้นจึงมักจะพิจารณาน้ำหนักเบามากขึ้น อย่างไรก็ตาม Stackless มีข้อ จำกัด อื่น ๆ อีกมากมายเช่นคุณไม่สามารถเรียกใช้รหัสที่ไม่มีข้อ จำกัด ภายในได้
วิธีการแก้ปัญหาหนึ่งในการแก้ปัญหาคือการทำให้ coroutines stackful เติบโตได้เพื่อใช้หน่วยความจำทางกายภาพตามความต้องการเมื่อต้องการจริงๆและมีวิธีที่ดีในการทำสิ่งนี้ขึ้นอยู่กับการจัดสรรหน่วยความจำเสมือนจริงเมื่อได้รับการสนับสนุนจากระบบปฏิบัติการ
หน่วยความจำเสมือนที่ได้รับการสนับสนุนจะสำรองหน่วยความจำเสมือนจริงในระบบปฏิบัติการสำหรับแต่ละสแต็ก coroutine แต่ยังไม่กระตุ้นการใช้หน่วยความจำทางกายภาพจริง ในขณะที่การใช้หน่วยความจำเสมือนจริงของแอปพลิเคชันจะสูงการใช้หน่วยความจำทางกายภาพจะต่ำและเติบโตตามความต้องการจริง (โดยปกติทุก 4kb ก้อนใน Linux)
หน่วยความจำเสมือนที่ได้รับการสนับสนุนจะเพิ่มขนาดสแต็กเริ่มต้นเป็นประมาณ 2MB โดยทั่วไปขนาดของเธรดพิเศษใน Linux ดังนั้นคุณจึงมีพื้นที่มากขึ้นใน coroutines ของคุณและความเสี่ยงของการล้นสแต็กต่ำ
ตัวอย่างเช่นการจัดสรร coroutines 100 พัน coroutines ที่มีพื้นที่สำรองสแต็กเกือบ 2MB พร้อมกับการจัดสรรหน่วยความจำเสมือนใช้การใช้งานหน่วยความจำทางกายภาพ 783MB ซึ่งมีประมาณ 8KB ต่อ coroutine อย่างไรก็ตามการใช้หน่วยความจำเสมือนจริงจะอยู่ที่ 98GB
ขอแนะนำให้เปิดใช้งานตัวเลือกนี้เฉพาะในกรณีที่คุณวางแผนที่จะวางไข่ coroutines นับพันในขณะที่ต้องการที่จะมีรอยเท้าหน่วยความจำต่ำ สภาพแวดล้อมทั้งหมดไม่ได้มีระบบปฏิบัติการที่มีการรองรับหน่วยความจำเสมือนดังนั้นตัวเลือกนี้จะถูกปิดใช้งานโดยค่าเริ่มต้น
ตัวเลือกนี้อาจเพิ่มลำดับของค่าใช้จ่ายเหนือศีรษะไปยัง mco_create() / mco_destroy() เนื่องจากพวกเขาจะขอให้ระบบปฏิบัติการจัดการตารางหน้าหน่วยความจำเสมือนจริงหากนี่เป็นปัญหาสำหรับคุณโปรดปรับแต่งการจัดสรรแบบกำหนดเองตามความต้องการของคุณเอง
สามารถกำหนดต่อไปนี้เพื่อเปลี่ยนพฤติกรรมของห้องสมุด:
MCO_API - ตัวคัดเลือก API สาธารณะ ค่าเริ่มต้นคือ externMCO_MIN_STACK_SIZE - ขนาดสแต็กขั้นต่ำเมื่อสร้าง coroutine ค่าเริ่มต้นคือ 32768 (32KB)MCO_DEFAULT_STORAGE_SIZE - ขนาดของบัฟเฟอร์ Coroutine Storage Buffer ค่าเริ่มต้นคือ 1024MCO_DEFAULT_STACK_SIZE - ขนาดสแต็กเริ่มต้นเมื่อสร้าง coroutine ค่าเริ่มต้นคือ 57344 (56KB) เมื่อ MCO_USE_VMEM_ALLOCATOR เป็นจริงค่าเริ่มต้นคือ 2040KB (เกือบ 2MB)MCO_ALLOC - ฟังก์ชั่นการจัดสรรเริ่มต้น ค่าเริ่มต้นคือ callocMCO_DEALLOC - ฟังก์ชั่นการจัดการเริ่มต้น ค่าเริ่มต้น freeMCO_USE_VMEM_ALLOCATOR - ใช้หน่วยความจำเสมือนสำรองสำรองการปรับปรุงรอยเท้าหน่วยความจำต่อ coroutineMCO_NO_DEFAULT_ALLOCATOR - ปิดใช้งานเครื่องจัดเก็บเริ่มต้นโดยใช้ MCO_ALLOC และ MCO_DEALLOCMCO_ZERO_MEMORY - หน่วยความจำศูนย์ของสแต็กเมื่อปรากฏขึ้นเพื่อจัดเก็บข้อมูลสำหรับสภาพแวดล้อมที่เก็บรวบรวมขยะMCO_DEBUG - เปิดใช้งานโหมดการดีบัก, บันทึกข้อผิดพลาดรันไทม์ใด ๆ เป็น stdout กำหนดโดยอัตโนมัติเว้นแต่จะมีการกำหนด NDEBUG หรือ MCO_NO_DEBUGMCO_NO_DEBUG - ปิดใช้งานโหมดดีบั๊กMCO_NO_MULTITHREAD - ปิดการใช้งานมัลติเธรด รองรับ MultIthRead เมื่อรองรับ thread_localMCO_USE_ASM - การใช้งานการใช้งานสวิทช์บริบทแอสเซมบลีMCO_USE_UCONTEXT - บังคับใช้การใช้งานสวิตช์บริบท UContextMCO_USE_FIBERS - บังคับใช้การใช้งานสวิตช์บริบทของเส้นใยMCO_USE_ASYNCIFY - การใช้บังคับของการใช้ Binaryen asyncify บริบทสวิตช์MCO_USE_VALGRIND - กำหนดว่าคุณต้องการเรียกใช้กับ valgrind เพื่อแก้ไขข้อผิดพลาดการเข้าถึงหน่วยความจำห้องสมุด Coroutine ถูกเปรียบเทียบสำหรับ X86_64 การนับ CPU รอบสำหรับสวิตช์บริบท (ทริกเกอร์ในประวัติย่อหรือผลผลิต) และการเริ่มต้น
| CPU Arch | ระบบปฏิบัติการ | วิธี | สวิตช์บริบท | เริ่มต้น | ทำให้ไม่เป็นประโยชน์ |
|---|---|---|---|---|---|
| x86_64 | ลินเวกซ์ | การประกอบ | 9 รอบ | 31 รอบ | 14 รอบ |
| x86_64 | ลินเวกซ์ | 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. */ต่อไปนี้เป็นตัวอย่างที่สมบูรณ์ยิ่งขึ้นการสร้างหมายเลข 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 สำหรับการจัดสรร coroutines หลายพันด้วยรอยเท้าหน่วยความจำต่ำซึ่งรวมถึงการเปลี่ยนแปลงการเปลี่ยนแปลงใน API จัดสรรฉันเป็นนักพัฒนาโอเพ่นซอร์สเต็มเวลาจำนวนเงินบริจาคผ่าน GitHub ของฉันจะได้รับการชื่นชมและสามารถนำกำลังใจให้ฉันเพื่อสนับสนุนโครงการนี้และโครงการโอเพ่นซอร์สอื่น ๆ ฉันอาจยอมรับการสนับสนุนครั้งเดียวสำหรับคุณสมบัติขนาดเล็กหรือการปรับปรุงเล็กน้อยสอดคล้องกับเป้าหมายของโครงการในกรณีนี้ติดต่อฉัน
ทางเลือกของคุณของโดเมนสาธารณะหรือ MIT ไม่มีการระบุแหล่งที่มาดูไฟล์ใบอนุญาต