GitHub - gtutino/CCo: Go-style coroutines in C · GitHub
/" data-turbo-transient="true" />
Skip to content
Search or jump to...
Search code, repositories, users, issues, pull requests...
-->
Search
Clear
Search syntax tips
Provide feedback
--><br>We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
-->
Name
Query
To see all available qualifiers, see our documentation.
Cancel
Create saved search
Sign in
/;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up
Appearance settings
Resetting focus
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
gtutino
CCo
Public
Notifications<br>You must be signed in to change notification settings
Fork
Star
main
BranchesTags
Go to file
CodeOpen more actions menu
Folders and files<br>NameNameLast commit message<br>Last commit date<br>Latest commit
History<br>60 Commits<br>60 Commits
include
include
src
src
test
test
.gitignore
.gitignore
LICENSE
LICENSE
Makefile
Makefile
README.md
README.md
main.c
main.c
View all files
Repository files navigation
CCo - C Coroutines
Overview
CCo implements Go-style coroutines in C.
Coroutines can use buffered and unbuffered channels to communicate in a message-passing style.
The coroutines can run in a single thread or in multiple threads using a thread pool of fixed os threads.
The API (include/cco.h) has all the functions to handle coroutines and channels,
if you already know Go programming language then the functions in the API will be familiar to you.
This project was made as an assignment for the Languages for Concurrency and Distribution course of the University of Padua, it's written in C (C11) and has no external dependencies.
[NOTE] This is just a fun personal project, so I don't recommend using it in industrial projects.
Architecture
Each coroutine has a context that saves the register state, the executing stack and infos about its running state.<br>This coroutines are organied in a circular queue (using a doubly linked list) and the "scheduling" is just round robin, so when a coroutine yields the current thread just looks at the next node of the list and do the context switch.
The context switch it's implemented in asm with custom procedures.
Coroutine free is handled automatically by the coroutine itself. When a coroutine starts, the address of a cleanup function is put at the top of the stack, so when the coroutine returns from the last stack frame it will jump to the cleanup function.
Thread Pool
Coroutines can run with multiple threads in a thread pool way, by default thread pool has N threads where N is the number of cpu<br>cores. By the way, CCO_THREADS enviroment var can be set to a fixed amount of thread by the user.
Each thread has its own local coroutines organized as said before (circular queue) and cannot access to other threads queues. So there is no thread syncronization when accessing local coroutines.
Then there is a global queue that can be used to pull and push coroutines by all the threads, so its access is handled by a mutex.
A thread will push to the global queue if its local coroutines are more of a fixed threshold (based on the total number of coroutines) and pull in the opposite situation, in this way all the threads will have (approximately) the same amount of coroutines.
This behaviour happens every time there is a context switch, since there is no preemption if the coroutines don't switch frequently there can be some misalignments.
Termination is handled by just looking at the total number of coroutines, when it reaches 0 the program terminates.
Channels
Channels can be used by multiple coroutines without limits, so they are general MxN channels.
They can be buffered (with a fixed size) or unbuffered, so the interaction can also be async.
The order of messages is always preserved.
A channel structure is basicall composed of two queues (send and recv queue) and the message data is just copied to destination through memcpy. All channels operation are (obviously) locked because they can be accessed by multiple threads.
The mutex are of type PTHREAD_MUTEX_RECURSIVE, this is needed because the select statement locks prevently the available channels and then do send/recv on the selected one, locking two times the same mutex.
Limitations
Coroutines can take up to 6 args of maximum 8 bytes each.
Select function can take up to 16 channel actions.
The stack of each coroutine is fixed to a constant value.
The code in src doesn't compile with compiler optimization and needs -fno-omit-frame-pointer flag for ensuring correct behaviour.
There is no preemption, so if a coroutine doesn't use yield or...