Zth (libzth)
Loading...
Searching...
No Matches
3_coop.cpp

Example how to yield properly.

Example how to yield properly.This program will print something like this (depending on the speed of your CPU):

Example 1: nice vs nicer
Be nice 0
Be nicer 0.0
Be nice 1
Be nicer 0.1
Be nice 2
Be nicer 0.2
Be nice 3
Be nicer 0.3
Be nice 4
Be nicer 0.4
Be nice 5
Be nicer 0.5
Be nice 6
Be nicer 0.6
Be nice 7
Be nicer 0.7
Be nice 8
Be nicer 0.8
Be nice 9
Be nicer 0.9
nice_fiber() done
Be nicer 1.0
Be nicer 1.1
Be nicer 1.2
Be nicer 1.3
Be nicer 1.4
Be nicer 1.5
Be nicer 1.6
Be nicer 1.7
<cut>
Be nicer 9.3
Be nicer 9.4
Be nicer 9.5
Be nicer 9.6
Be nicer 9.7
Be nicer 9.8
Be nicer 9.9
nicer_fiber() done

Example 2: server-client
Requesting 0
Serving 0
Requesting 1
Serving 1
Requesting 2
Serving 2
Requesting 3
Serving 3
Requesting 4
Serving 4
Requesting 5
Serving 5
Requesting 6
Serving 6
Requesting 7
Serving 7
Requesting 8
Serving 8
Requesting 9
Serving 9
/*
* SPDX-FileCopyrightText: 2019-2026 Jochem Rutgers
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <zth>
#include <deque>
// No Zth function will yield the processor, except for the obvious ones (zth::yield(), zth::nap(),
// etc.) So, you must make sure that you relinquish the processor often enough to allow other fibers
// to proceed. Essentially, there is way to do this: zth::yield(). When zth::yield() is called,
// the scheduler of the current worker is invoked and a context switch is made to the next fiber in
// line.
// There is a catch. Consider the following example:
static void do_work(int amount)
{
zth::Timestamp end = zth::Timestamp::now() + 5e-3F * (float)amount;
while(!end.hasPassed())
; // busy wait
}
void nice_fiber()
{
for(int i = 0; i < 10; i++) {
printf("Be nice %d\n", i);
do_work(10); // do some work
}
printf("nice_fiber() done\n");
}
void nicer_fiber()
{
for(int i = 0; i < 10; i++) {
// I am going to do do_work(10) too, like nice_fiber(), but I
// am not sure how much time that would take without yielding.
// So, I am really nice to split the work in small packets.
for(int w = 0; w < 10; w++) {
printf("Be nicer %d.%d\n", i, w);
do_work(1);
}
}
printf("nicer_fiber() done\n");
}
void example_1()
{
printf("Example 1: nice vs nicer\n");
zth::fiber_future<> nice = zth::fiber(nice_fiber);
zth::fiber_future<> nicer = zth::fiber(nicer_fiber);
*nice;
*nicer;
}
// As nicer_fiber() yields a 10 times more often than nice_fiber(), nice_fiber() make significantly
// more progress; nice_fiber() finishes before nicer_fiber() gets to its second iteration. That is
// not fair. Zth implements the following: every fiber gets at least zth::Config::MinTimeslice()
// time, regardless of how many times zth::yield() is called. If a fiber calls yield more often,
// these calls are just ignored as long as we are within this time slice.
// So, if every fiber yields at least once in this time period, the CPU is fairly shared among all
// fibers. For your application, determine a realistic and usable MinTimeslice() value, and make
// sure (by profiling) that you yield often enough.
// Now a new question pops up, what if you really want to yield within this time slice? Call
// zth::outOfWork() instead. This says 'give up the CPU, I don't have any more work to do, so there
// is no need to keep me running'. This is commonly used when polling.
static bool terminate_server = false;
static std::deque<int> work;
void server()
{
while(true) {
if(!work.empty()) {
printf("Serving %d\n", work.front());
work.pop_front();
// I am nice...
} else if(terminate_server) {
break;
} else {
// If we would call zth::yield() here, we would effectively do a useless
// busy-wait for zth::Config::MinTimeslice() before client() would get a
// chance to enqueue more work.
}
}
}
void client()
{
for(int i = 0; i < 10; i++) {
printf("Requesting %d\n", i);
do_work(10);
work.push_back(i);
}
}
void example_2()
{
printf("\nExample 2: server-client\n");
*c;
terminate_server = true;
*s;
}
int main_fiber(int /*argc*/, char** /*argv*/)
{
example_1();
example_2();
return 0;
}
Convenient wrapper around struct timespec that contains an absolute timestamp.
Definition time.h:568
static Timestamp now()
Definition time.h:595
bool hasPassed() const noexcept
Definition time.h:624
int main_fiber(int argc, char **argv)
Definition main.cpp:11
void outOfWork()
Force a context switch.
Definition worker.h:476
fiber_type< F >::fiber fiber(F &&f, Args &&... args)
Create and start a new fiber.
Definition async.h:1192
void yield(Fiber *preferFiber=nullptr, bool alwaysYield=false, Timestamp const &now=Timestamp::now())
Allow a context switch.
Definition worker.h:454
The future returned by a fiber.
Definition async.h:1292