Intro
libtask is a coroutine library that has cooperative scheduler and channel
built-in. The channel
is a multi-sender multi-receiver structure that could be used for synchronization and data transferring between coroutines.
We will use “task” to refer to “coroutine” in the following for coherency.
Internal
overview of running procedure
There are 4 exclusive states for any task given:
- running
- suspended, put in the task queue
- suspended, put in a channel’s sender queue or receiver queue
- terminated
The program exit in 2 situations:
- no running task, no suspended task → exit normally
- no running task, having suspended task → exit with some task “stalled”
basic data structures
global variables & states
Tasklist taskrunqueue
: a double-linked queue contains all tasks which are waiting-for-execute, the task was added to queue via append-to-tail, never inserted directly into the middle of the queueContext taskschedcontext
: act as a breakpoint in the procedure of context switchTask *taskrunning
: the task running currentlyint taskcount
: total number of tasks not exited (running + suspended)
a simple send
code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <task.h>
Channel *c;
void task_2(void *arg)
{
printf("task_2 start\n");
unsigned long v = chanrecvul(c);
printf("received: %lu\n", v);
}
void taskmain(int argc, char **argv)
{
c = chancreate(sizeof(unsigned long), 3);
taskcreate(task_2, c, 32768);
unsigned long v = 12345;
printf("going to send number: %lu\n", v);
chansendul(c, v);
printf("send success: %lu\n", v);
}
output:
🍚 build git:(master) ✗ ./bin/chan_1
run 1 ()
going to send number: 12345
send success: 12345
back in scheduler
run 2 ()
task_2 start
received: 12345
back in scheduler
illustration of procedure:
sending multiple values without any receiver
code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <task.h>
Channel *c;
void taskmain(int argc, char **argv)
{
c = chancreate(sizeof(unsigned long), 3);
for (unsigned long i = 0; i < 10; i++)
{
printf("going to send number: %lu\n", i);
chansendul(c, i);
printf("send success: %lu\n", i);
}
printf("taskmain end\n");
}
output:
🍚 build git:(master) ✗ ./bin/chan_1
run 1 ()
going to send number: 0
send success: 0
going to send number: 1
send success: 1
going to send number: 2
send success: 2
going to send number: 3
back in scheduler
no runnable tasks! 1 tasks stalled
illustration of procedure:
sending multiple values to a receiver
code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <task.h>
Channel *c;
void task_2(void *arg)
{
printf("task_2 start\n");
for (int i = 0; i < 10; i++)
{
unsigned long v = chanrecvul(c);
printf("received: %lu\n", v);
}
printf("task_2 end\n");
}
void taskmain(int argc, char **argv)
{
c = chancreate(sizeof(unsigned long), 3);
taskcreate(task_2, c, 32768);
for (unsigned long i = 0; i < 10; i++)
{
printf("going to send number: %lu\n", i);
chansendul(c, i);
printf("send success: %lu\n", i);
}
printf("taskmain end\n");
}
output:
🍚 build git:(master) ✗ ./bin/chan_3
run 1 ()
going to send number: 0
send success: 0
going to send number: 1
send success: 1
going to send number: 2
send success: 2
going to send number: 3
back in scheduler
run 2 ()
task_2 start
received: 0
received: 1
received: 2
received: 3
back in scheduler
run 1 (taskmain)
send success: 3
going to send number: 4
send success: 4
going to send number: 5
send success: 5
going to send number: 6
send success: 6
going to send number: 7
send success: 7
going to send number: 8
back in scheduler
run 2 ()
received: 4
received: 5
received: 6
received: 7
received: 8
back in scheduler
run 1 (taskmain)
send success: 8
going to send number: 9
send success: 9
taskmain end
back in scheduler
run 2 ()
received: 9
task_2 end
back in scheduler
illustration of procedure:
pondering
-
How context switching was achieved in
libtask
? -
What mechanism the scheduler of
libtask
use to determine which coroutine should be executed next?Run the task at the head of the
taskrunqueue
. -
What is the timing of context switching between coroutines? Via cooperative or preemptive?
Cooperative. A task voluntarily relinquished its control when it cannot move on at this moment. Specifically, there are the following situations:
- transferring control actively by calling
taskswitch()
- send to a channel which is already filled
- receive from an empty channel
- transferring control actively by calling
-
How a channel maintaining its senders and receivers?
Via
Alt
struct, which includes all pieces of information of a send/receive action:- Task that perform this action
- Action
- Value
- Target channel
-
If a task makes a send action to a channel with many receivers waiting on it, which receiver would be chosen to receive the element and go back to the task queue? Why?
A random receiver would be chosen because all receivers are considered to have equal opportunity.
-
If a task makes a receive action to a channel with many senders blocked, which sender would be chosen to put the element to buffer succeed and go back to the task queue? Why?
A random sender would be chosen because all senders are considered to have equal opportunity.