Website logo: pixel art of a black cat with a yellow background

Bruno
Costa

utask

From January 2022 to present

https://github.com/brunexgeek/utask

utask is a single file library implementing userspace cooperative multitasking inside native threads. Tasks have many-to-one relantionship with native threads (i.e. each thread has its own tasks) and you can create up to 128 tasks per thread.

By default, utask can be used only by one thread. If you want to use it in more threads, define UTASK_MULTI_THREAD before including utask.h. Defining UTASK_MULTI_THREAD incurs additional overhead to retrieve per thread internal information.

This library requires ucontext API provided by GNU/Linux and some other operating systems.

Usage

Include utask.h in your project. You can include utask.h in any source file, but one of them (and only one) must define UTASK_IMPL before including it. That tells utask to define the library implementation. If you want to use tasks in more than one thread, also defines UTASK_MULTI_THREAD.

/* #define UTASK_MULTI_THREAD */ #define UTASK_IMPL #include "utask.h"

Before using any function you must initialize the task scheduler in the current thread with utask_initialize. The default number of tasks is 8 and the default stack size is 16 KiB. Always check the return value for errors.

int result = utask_initialize(2, UT_DEFAULT, UT_DEFAULT); if (result != UT_EOK) abort("Failure");

Now just create some tasks and start the task scheduler to run them. The function utask_run will block the execution until all tasks are finished. Make sure that inside my_function_1 and my_function_2 you call utask_yield regularly to give up processor time to other tasks. You can also create tasks inside other tasks.

utask_create(my_function_1, NULL); utask_create(my_function_2, NULL); utask_run(); printf("Finished running all tasks!\n");

Remember to terminate the task scheduler to release the allocated resources.

utask_terminate();

API

utask_initialize

int utask_initialize( int max, int stack_size, int flags )

Initialize the task scheduler for the current thread.

If UTASK_MULTI_THREAD is not defined before utask.h is included only one thread can use tasks. Otherwise, this function can be called for each thread to initialize its task scheduler.

This function must be called outside a task.

utask_terminate

int utask_terminate()

Terminate the task scheduler for the current thread.

This function must be called outside a task.

utask_create

int utask_create(void (*func)(void*), void *arg)

Create a new task and add it in the task scheduler’s running queue. The return value is the task identifier (same value returned by utask_id function).

This function can be called inside and outside a task.

utask_run

int utask_run()

Run the task scheduler until all tasks are completed.

Tasks are selected via round-robin algorithm.

utask_yield

int utask_yield()

Relinquish control and enables the task scheduler run another task.

utask_id

int utask_id()

Returns the current task identifier.

utask_count

int utask_count()

Returns the number of tasks in runnable state.

utask_info

int utask_info( utask_info_t *info )

Returns information about the task scheduler state.

Stack size

There is no simple and portable way to accurately check for stack overflows. Furthermore, the task stack does not grow as needed, as operating systems usually do with threads. The library attempts to perform rudimentary validations to detect possible stack overflows, but this is not sufficient to ensure execution stability (especially if the call graph is deep). Be sure to test your application to verify that the specified stack size is sufficient for various use cases.