C++ Programlama Dili: Eşyordamlar için karmaşık bir öncelik zamanlayıcı

Adanali

Active member
C++ Programlama Dili: Eşyordamlar için karmaşık bir öncelik zamanlayıcı


  1. C++ Programlama Dili: Eşyordamlar için karmaşık bir öncelik zamanlayıcı

Bu, C++ eşyordamları için zamanlayıcılar hakkındaki mini dizimin dördüncü makalesi. İlk iki makale Dian-Lun Lin’in konuk gönderileriydi:

Duyuru








Rainer Grimm uzun yıllardır yazılım mimarı, ekip ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanıyor, aynı zamanda özel konferanslarda sık sık konuşmaktan da hoşlanıyor. Modern C++ adlı blogunda C++ tutkusunu yoğun bir şekilde ele alıyor.







  1. Yazılım Mühendisliği: Dian-Lun Li’den Coroutine’lere Kompakt Bir Giriş
  2. Coroutine: görevler için bir zamanlayıcı – Bölüm 2, Dian-Lun Li
  3. C++ Programlama Dili: Eşyordamlar için öncelikli zamanlayıcı







Dian-Lun planlayıcıları konteyner adaptörünü temel alıyordu std::stack VE std::queue. THE std::stack Görevlerini son giren ilk çıkar stratejisine göre planlar, ancak std::queue ilk giren ilk çıkar uygulanır. Sonunda destekçilerim std::priority_queueGörevleri önceliklendirmek için en son makaleyi temel alan planlayıcı.

Bu makalede son zamanlayıcıyı iki şekilde geliştireceğim. İlk önce karşılaştırma fonksiyonuyla başlamak istiyorum. std::priority_queue oynamak.

Karşılaştırma işlevi


Öncelik kuyruğunda her zaman en büyük öğe en üstte bulunur. std::priority_queue karşılaştırma operatörü varsayılan olarak kullanılır std::less. Aşağıdaki zamanlayıcı, model parametresi olarak karşılaştırma işlevine sahiptir.


// priority_queueSchedulerComparator.cpp

#include <concepts>
#include <coroutine>
#include <functional>
#include <iostream>
#include <queue>
#include <utility>


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<promise_type>::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle<promise_type> handle): handle{handle}{}

auto get_handle() { return handle; }

std::coroutine_handle<promise_type> handle;
};

using job = std::pair<int, std::coroutine_handle<>>; // (1)

template <typename Comparator = std::ranges::less> // (2)
requires std::predicate<decltype(Comparator()), job, job> // (3)
class Scheduler {

std::priority_queue<job, std::vector<job>, Comparator> _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 << name << " startn";
co_await std::suspend_always();
std::cout << name << " executen";
co_await std::suspend_always();
std::cout << name << " finishn";
}


int main() {

std::cout << 'n';

Scheduler scheduler1; // (4)

scheduler1.emplace(0, createTask("TaskA").get_handle());
scheduler1.emplace(1, createTask(" TaskB").get_handle());

scheduler1.schedule();

std::cout << 'n';

Scheduler<std::ranges::greater> scheduler2; // (5)

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

scheduler2.schedule();

std::cout << 'n';

}


Zamanlayıcıyı çok bunaltıcı bulanlar serinin önceki yazılarını okumalıdır. Öncelikle öncelik ve görevin (1) birleşimine iş adını veriyorum. Zamanlayıcı bir model parametresi gerektirir Comparatorhangisi varsayılan std::ranges::less (2)’dir. Konsept de test ediliyor std:::predicate (3)’te, yüklem iki işle çağrılabiliyorsa.

scheduler1 (5) en yüksek öncelikli işten başlayın, scheduler2 (6) bunun yerine en düşük öncelikli işle.








(6)’da işi planlayıcıya reddediyorum. Ertelenen bir işe yeniden öncelik verebilseydiniz güzel olmaz mıydı?

Güncelleme önceliği


Aşağıdaki zamanlayıcı, ertelenen bir işin önceliğini de güncelleyebilir.


// priority_queueSchedulerPriority.cpp

#include <concepts>
#include <coroutine>
#include <functional>
#include <iostream>
#include <queue>
#include <utility>


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<promise_type>::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle<promise_type> handle): handle{handle}{}

auto get_handle() { return handle; }

std::coroutine_handle<promise_type> handle;
};

using job = std::pair<int, std::coroutine_handle<>>;

template <typename Updater = std::identity, // (1)
typename Comperator = std::ranges::less>
requires std::invocable<decltype(Updater()), int> && // (2)
std::predicate<decltype(Comperator()), job, job>
class Scheduler {

std::priority_queue<job, std::vector<job>, Comperator> _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 << name << " startn";
co_await std::suspend_always();
for (int i = 0; i <= 3; ++i ) {
std::cout << name << " execute " << i << "n"; // (5)
co_await std::suspend_always();
}
co_await std::suspend_always();
std::cout << name << " finishn";
}


int main() {

std::cout << 'n';

Scheduler scheduler1; // (6)

scheduler1.emplace(0, createTask("TaskA").get_handle());
scheduler1.emplace(1, createTask(" TaskB").get_handle());
scheduler1.emplace(2, createTask(" TaskC").get_handle());

scheduler1.schedule();

std::cout << 'n';

Scheduler<decltype([](int a) { return a - 1; })> scheduler2; //(7)

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

scheduler2.schedule();

std::cout << 'n';

}


Programda yalnızca birkaç zamanlayıcı değişikliği var priority_queueSchedulerComparator.cpp önceliklerin güncellenmesini desteklemek için gereklidir.

İlk olarak, zamanlayıcı ek bir model parametresi alır Updater (1), varsayılan ayar std::identity kullanılmış. Updater aranabilir olmalı ve bir int farz etmek. Açıkçası öyle std::invocable bir kavram (2). THE Updater (3)’te yaratılır ve (4)’te uygulanır. Ayrıca (5)’teki eşyordam görevin hangi bölümünün gerçekleştirildiğini gösterir. scheduler1 (6) görevini en yüksek öncelikten başlayarak yürütür, ancak scheduler2 (7) ertelenen bir görevin önceliğini bir azaltır. Çağrılabilir birim olarak lambda kullanıyorum.

Program çıktısı farklı planlama stratejilerini gösterir.








Sıradaki ne?


Eşyordamlar, eşzamansız kod yazmanın sezgisel bir yolunu sağlar. Bir sonraki makalem, Ljubic Damir’in ortak rutin tabanlı Tek Üretici – Tek Tüketici iş akışını sunduğu konuk gönderisi olacak.


(kendim)



Haberin Sonu
 
Üst