Fiber
C++20 fiber implementation with similar interface to std::thread, header-only / x86_64 / Linux only / stackful / built-in scheduler / thread shareable
Install / Use
/learn @fix8mt/FiberREADME
fiber
A novel C++20 fiber implementation with similar interface to std::thread, header-only / x86_64 / Linux only / stackful / built-in scheduler / thread shareable
Introduction
This is a novel fiber implementation with a similar interface to std::thread. Taking any callable
object, fibers execute and cooperatively yield amongst
themselves and the calling function all within a single thread. Using fibers, single threaded applications can be written as though they were multi-threaded,
with the advantage that no concurrency controls are required.
For multi-threaded applications, each thread maintains its own list of running fibers, leaving the user to implement their own concurrency controls. This
implementation allows you to move fibers between threads. This can be used to share work or scale an application by adding more fibers to new or existing threads.
Currently only Linux/x86_64 is supported. Other platforms to be supported in the future.
|
|
|:--:|
| Screenshot from montest2 with 20 fibers working in one thread and using fiber_monitor|
Quick links
|Link|Description|
--|--
|Wiki| for complete documentation|
|API| for API documentation|
|Building| for build options and settings|
|Monitor| for built-in monitor documentation|
|Examples| for details about all the included examples|
|Here| for implementation|
|Here| for the original f8fiber implementation|
Motivation
- header-only
std::threadlike interface- no external dependencies
- simplicity, lightweight
- make use of C++20 features
constexprwhere possible- expand and improve interface
Features
- x86_64 Linux only
- single header-only
- stackful fibers; stacksize configurable for each fiber
- supports any callable object (eg. function, class member, lambda expression) with optional arguments
- heap based, memory-mapped or placement stacks; user definable stack can also be used
- context switch implemented with inline assembly
- fibers can be moved to other threads (can be configured out for performance)
std::invokestyle ctor, with arguments perfectly forwarded to the callable- extended API, supporting
resume,resume_if,resume_with,kill,kill_all,suspend,schedule,join,join_if,detach,resume_main,schedule_if,move,suspend_if,wait_alland more - built-in fiber printer
- optional built-in terminal based monitor
- helper templates including
async,make_fiber,launch_allandlaunch_all_with_paramsand more - supports
fibersandthis_fibernamespaces - ctor based fiber parameter struct (POD) - including fiber name, custom stack and stack size, launch policy, launch order and auto join
- built-in round-robin scheduler
- can be used with
std::packaged_task,std::future,std::promiseand so forth - fast, very lightweight
- fiber exceptions and general exception handling
- built-in instrumentation (can be configured out for performance)
- lots of examples
- full API documentation
- supports
jfiber(similar tostd::jthread) - works with gcc, clang
Examples
1. A simple resumable function
In the following example each iteration of main and func simply yields, printing the iteration count before and after each yield.
Note to pass a reference to the flags variable we need to use the std::ref wrapper. Before exiting func calls the built-in printer.
When func exits, control returns to main where testing f0 for non-zero returns false indicating the fiber has finished.
Note that you can name a fiber using fiber_params. The default name for the calling thread of a fiber is main.
#include <iostream>
#include <iomanip>
#include <thread>
#include <string>
#include <fix8/fiber.hpp>
using namespace FIX8;
void func(bool& flags, int cnt)
{
std::cout << '\t' << this_fiber::name() << " (fiber id:" << this_fiber::get_id() << ")\n";
for (int kk{}; kk < cnt; ++kk)
{
std::cout << '\t' << this_fiber::name() << ':' << kk << '\n';
this_fiber::yield();
std::cout << '\t' << this_fiber::name() << ':' << kk << " (resumed)\n";
}
flags = true;
fibers::print();
std::cout << '\t' << this_fiber::name() << ":exit\n";
}
int main(int argc, char *argv[])
{
std::cout << this_fiber::name() << ":entry (fiber id:" << this_fiber::get_id() << ")\n";
bool flags{};
fiber f0({"func"}, &func, std::ref(flags), 5);
std::cout << "flags=" << std::boolalpha << flags << '\n';
for (int ii{}; f0; ++ii)
{
std::cout << this_fiber::name() << ':' << ii << '\n';
this_fiber::yield();
std::cout << this_fiber::name() << ':' << ii << " (resumed)\n";
}
std::cout << "flags=" << std::boolalpha << flags << '\n';
std::cout << this_fiber::name() << ":exit\n";
return 0;
}
</p>
</details>
<details><summary><i>output</i></summary>
<p>
$ ./fibertest0
main:entry (fiber id:NaF)
flags=false
main:0
func:entry (fiber id:2896)
func:0
main:0 (resumed)
main:1
func:0 (resumed)
func:1
main:1 (resumed)
main:2
func:1 (resumed)
func:2
main:2 (resumed)
main:3
func:2 (resumed)
func:3
main:3 (resumed)
main:4
func:3 (resumed)
func:4
main:4 (resumed)
main:5
func:4 (resumed)
# fid pfid prev ctxs stack ptr stack alloc depth stacksz flags ord name
0 * 2896 NaF NaF 6 0x7f9fb3632ea8 0x7f9fb3613010 352 131072 _________ 99 func
1 NaF NaF 2896 6 0x7fff7628f8a8 0 0 8388608 m________ 99 main
func:exit
main:5 (resumed)
flags=true
main:exit
$
</p>
</details>
2. A generator class
The following example implements a simple generator pattern.
Both the producer() and consumer() methods of foo are passed as callable objects to the fiber members _produce and _consume.
Note that the fiber ctor requires this for pointer to member functions. Also note the additional parameter num passed to _produce. Your callable object definition
must match the parameters passed to fiber.
After construction of the fibers, the foo ctor calls _produce.resume() which immediately switches to producer().
Each iteration of the for loop generates 5 random numbers that are pushed to a queue after which producer() switches to consumer().
Note that consumer() will only continue to run while the _produce fiber is still running, consuming all numbers pushed to the queue and when empty
switching back to producer().
When producer() has looped numtogen times, it calls _consume.schedule() which instructs the fiber scheduler to schedule consumer() as the next fiber to run;
producer() then exits and consumer() is resumed (exiting a fiber causes the scheduler to switch to the next scheduled fiber) -
but since the _produce fiber has exited, while (_produce) will return false causing consumer() to also exit.
#include <queue>
#include <random>
#include <fix8/fiber.hpp>
using namespace FIX8;
class foo
{
std::queue<long> _queue;
fiber _produce, _consume;
void producer(int numtogen)
{
std::cout << "\tproducer:entry (id:" << this_fiber::get_id() << ")\n";
std::mt19937_64 rnde {std::random_device{}()};
auto dist{std::uniform_int_distribution<long>(1, std::numeric_limits<long>().max())};
for (; numtogen; --numtogen)
{
while(_queue.size() < 5)
_queue.push(dist(rnde));
std::cout << "\tproduced: " << _queue.size() << '\n';
_consume.resume(); // switch to consumer
}
_consume.schedule(); // consumer is next fiber to run
std::cout << "\tproducer:exit\n";
}
void consumer()
{
std::cout << "\tconsumer:entry (id:" << this_fiber::get_id() << ")\n";
while (_produce)
{
int cnt{};
while(!_queue.empty())
{
std::cout << "\t\t" << ++cnt << ": " << _queue.front() << '\n';
_queue.pop();
}
std::cout << "\tconsumed: " << cnt << '\n';
_produce.resume(); // switch to producer
}
std::cout << "\tconsumer:exit\n";
}
public:
foo(int num) : _produce(&foo::producer, this, num), _consume(&foo::consumer, this)
{
_produce.resume(); // switch to producer
}
};
int main(int argc, char *argv[])
{
std::cout << "main:entry\n";
foo bar(argc > 1 ? std::stoi(argv[1]) : 10);
std::cout << "main:exit\n";
return 0;
}
</p>
</details>
<details><summary><i>output</i></summary>
<p>
$ ./fibertest22 5
main:entry
producer:entry (id:4016)
produced: 5
consumer:entry (id:1968)
1: 1349602525532272804
2: 316583067409009491
3: 1020347932332564514
4: 3829997051190076899
5: 947478241006829049
consumed: 5
produced: 5
1: 3617976082375098752
2: 3972849389773859934
3: 7998668485491537141
4: 4650831140486621276
5: 7332043501
