hello-world Tutorial

This tutorial shows the basic operation of poco and showcases the scheduler’s operation. The goal here is to have a program that prints the string Hello World! 5 times, then exits.

In this tutorial, you will be exposed to the following poco concepts.

  1. Task creation.

  2. Scheduler use.

  3. Yielding.

Note

The hello world sample is located within the project under samples/hello-world. If using CMake, the target sample_hello_world builds this example.

The full program snippet can be found at the bottom of this page.

Overview

The operation of this program uses 2 tasks. A task for printing the string Hello ``, which we will call the ``hello_task, and the other for printing World!\n, which is will be called the world_task.

The execution plan can be summarised below.

@startuml

concise "hello" as H
concise "world" as W
scale 1 as 100 pixels
hide time-axis

@0
H is "Hello"
W is {-}

@1
H is {-}
W is "World!"
H-> W: yield()

@2
H is "Hello"
W is {-}
W-> H: yield()

@3
H is {-}
W is "World!"
H-> W: yield()

@enduml

Execution plan for tasks.

Defining the Tasks

Thinking about the tasks in isolation, each task will either print the value Hello or World!\n the console, then wait until the next task.

Each task function has a user provided context. This context allows programs to pass in references to items the task requires to operate. In this tutorial, the additional context is not required.

The sample below shows the implementation for the task printing Hello. This can be implemented using normal flow control, and does not require any special handling.

void hello_task(void *context) {
    (void)context;
    for (int i = 0; i < 5; ++i) {
        printf("Hello ");
        coro_yield();
    }
}

The key line is where the yield takes place. This instructs the scheduler to pause the current execution of the function, and move on to the next task. When the scheduler returns to this function, it will resume where it left off.

We can also make the World\n task in the same manner.

    (void)context;
    for (int i = 0; i < 5; ++i) {
        printf("World!\n");
        coro_yield();
    }
}

int main() {

Coroutine Creation

Each function that will be run as a task will need to be associated with a single coroutine structure. These can be defined in the program’s main.

Each of the calls to coro_create will create a coroutine structure used by the scheduler. This includes all the necessary information to allow coroutines to pause and resume execution.

    Coro *tasks[] = {
        coro_create(hello_task, NULL, STACK_SIZE),
        coro_create(world_task, NULL, STACK_SIZE),
    };

    Scheduler *scheduler =
        round_robin_scheduler_create(tasks, sizeof(tasks) / sizeof(tasks[0]));

Each call takes in 3 arguments, the function this coroutine will run, a user provided context, and the size of stack to allocate to this coroutine.

As this is a simple example, we can use the platform provided default stack sizes, as well as setting the context to NULL. Any context provided here will be passed to the function signature.

The second step involves creation of the scheduler. poco provides some basic scheduler implementations to use. In this example, we will use round_robin_scheduler as it provided the behaviour we require (alternating between all tasks).

Every scheduler implements a generic scheduler interface to allow for implementations to support a wider variety of schedulers if needed. Once created, we can run using the scheduler_run() method to run the freshly created scheduler.


    if (scheduler == NULL) {
        printf("Failed to create scheduler\n");
        return -1;
    }

    scheduler_run(scheduler);

    return 0;
}

Running the Program

Execution of the chosen scheduler always begins with the first task in the list. In this example, it would be the hello task.

This results in the printouts to the console.

Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

Full Reference

// SPDX-FileCopyrightText: Copyright contributors to the poco project.
// SPDX-License-Identifier: MIT
/*!
 * @file
 * @brief Hello world example, performs the classic hello world example using
 * coroutines.
 *
 * Each task sends either Hello, or World!, and alternates between the two tasks.
 *
 * The result should be 5 instances of the string "Hello world" printed to stdout.
 *
 * This example uses the basic round robin scheduler.
 */

#include <poco/poco.h>
#include <stdio.h>

#define STACK_SIZE (DEFAULT_STACK_SIZE)

void hello_task(void *context) {
    (void)context;
    for (int i = 0; i < 5; ++i) {
        printf("Hello ");
        coro_yield();
    }
}

void world_task(void *context) {
    (void)context;
    for (int i = 0; i < 5; ++i) {
        printf("World!\n");
        coro_yield();
    }
}

int main() {

    Coro *tasks[] = {
        coro_create(hello_task, NULL, STACK_SIZE),
        coro_create(world_task, NULL, STACK_SIZE),
    };

    Scheduler *scheduler =
        round_robin_scheduler_create(tasks, sizeof(tasks) / sizeof(tasks[0]));

    if (scheduler == NULL) {
        printf("Failed to create scheduler\n");
        return -1;
    }

    scheduler_run(scheduler);

    return 0;
}