Home » C++ programming language: A sophisticated priority scheduler for coroutines

C++ programming language: A sophisticated priority scheduler for coroutines

by admin
C++ programming language: A sophisticated priority scheduler for coroutines

C++ programming language: A sophisticated priority scheduler for coroutines

This is the fourth article in my mini-series about schedulers for C++ coroutines. The first two articles were guest posts by Dian-Lun Lin:

Advertisement

Rainer Grimm has been working as a software architect, team and training manager for many years. He enjoys writing articles on the programming languages ​​C++, Python and Haskell, but also enjoys speaking frequently at specialist conferences. On his blog Modern C++ he deals intensively with his passion C++.

Software Engineering: A Concise Introduction to Coroutines by Dian-Lun LiCoroutines: A Scheduler for Tasks – Part 2 by Dian-Lun LiC++ Programming Language: A Priority Scheduler for Coroutines

Dian-Lun’s schedulers were based on the container adapter std::stack and std::queue. The std::stack schedules its tasks according to the last-in first-out strategy, but the std::queue uses first-in first-out. Finally, my std::priority_queue based scheduler from the last article supports task prioritization.

In this article I will improve the final scheduler in two ways. First, I want to play with the compare function of std::priority_queue.

The priority queue always has its largest item at the top. std::priority_queue uses the comparison operator std::less by default. The following scheduler has the comparison function as a template parameter.

// priority_queueSchedulerComparator.cpp

#include
#include
#include
#include
#include
#include

struct Task {

struct promise_type {
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }

Task get_return_object() {
return std::coroutine_handle::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle handle): handle{handle}{}

auto get_handle() { return handle; }

std::coroutine_handle handle;
};

using job = std::pair>; // (1)

template // (2)
requires std::predicate // (3)
class Scheduler {

See also  Technology Diary — January 17, 2024

std::priority_queueComparator> _prioTasks;

public:

void emplace(int prio, std::coroutine_handle task) {
_prioTasks.push(std::make_pair(prio, task));
}

void schedule() {
while(!_prioTasks.empty()) {
auto [prio, task] = _prioTasks.top();
_prioTasks.pop();
task.resume();

if(!task.done()) {
_prioTasks.push(std::make_pair(prio, task)); // (6)
}
else {
task.destroy();
}
}
}

};

Task createTask(const std::string& name) {
std::cout scheduler2; // (5)

scheduler2.emplace(0, createTask(“TaskA”).get_handle());
scheduler2.emplace(1, createTask(” TaskB”).get_handle());

scheduler2.schedule();

std::cout Those who find the scheduler too overwhelming should read the previous articles in the series. First, I call the combination of a priority and a task a job (1). The scheduler requires a template parameter Comparator, which defaults to std::ranges::less (2). In addition, the concept std:::predicate in (3) checks whether the predicate can be called with two jobs.

scheduler1 (5) starts with the highest priority job, while scheduler2 (6) starts with the lowest priority job.

In (6) I push the job back to the scheduler. Wouldn’t it be nice if I could change the priority of a pushed-back job?

The following scheduler can additionally update the priority of a pushed back job.

// priority_queueSchedulerPriority.cpp

#include
#include
#include
#include
#include
#include

struct Task {

struct promise_type {
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }

Task get_return_object() {
return std::coroutine_handle::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle handle): handle{handle}{}

auto get_handle() { return handle; }

std::coroutine_handle handle;
};

using job = std::pair>;

template
requires std::invocable && // (2)
std::predicate
class Scheduler {

std::priority_queueComperator> _prioTasks;

public:
void emplace(int prio, std::coroutine_handle task) {
_prioTasks.push(std::make_pair(prio, task));
}

void schedule() {
Updater upd = {}; // (3)
while(!_prioTasks.empty()) {
auto [prio, task] = _prioTasks.top();
_prioTasks.pop();
task.resume();

if(!task.done()) {
_prioTasks.push(std::make_pair(upd(prio), task)); // (4)
}
else {
task.destroy();
}
}
}

};

Task createTask(const std::string& name) {
std::cout scheduler2; //(7)

scheduler2.emplace(0, createTask(“TaskA”).get_handle());
scheduler2.emplace(1, createTask(” TaskB”).get_handle());
scheduler2.emplace(2, createTask(” TaskC”).get_handle());

See also  The formatting library in C++20: Details about the format string

scheduler2.schedule();

std::cout Only a few changes are necessary to the scheduler in the priority_queueSchedulerComparator.cpp program to support updating priorities.

First, the scheduler receives an additional template parameter Updater (1), which uses std::identity by default. Updater must be callable and accept an int. Of course, std::invocable is a concept (2). The updater is created in (3) and applied in (4). In addition, the coroutine in (5) indicates which part of the task is being executed. scheduler1 (6) executes its task starting with the highest priority, but scheduler2 (7) lowers the priority of a deferred task by one. I’m using a lambda as a callable unit.

The output of the program shows the different scheduling strategies.

Coroutines provide an intuitive way to write asynchronous code. My next article will be a guest post by Ljubic Damir in which he presents a Single Producer – Single Consumer workflow based on coroutines. (rme)

To home page

You may also like

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy