Düzeltme: C++ blogundaki eşyordamlar için öncelik zamanlayıcıdaki hata

Adanali

Active member
Düzeltme: C++ blogundaki eşyordamlar için öncelik zamanlayıcıdaki hata
Son iki blog yazımda eşyordamlar için öncelik planlayıcıyı tanıttım. Bunun kodunda bir hata vardı.

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.













Bozuk zamanlayıcı şöyle görünür:


// priority_queueSchedulerPriority.cpp

#include <concepts>&#13;
#include <coroutine>&#13;
#include <functional>&#13;
#include <iostream>&#13;
#include <queue>&#13;
#include <utility>&#13;
&#13;
&#13;
struct Task {&#13;
&#13;
struct promise_type {&#13;
std::suspend_always initial_suspend() noexcept { return {}; }&#13;
std::suspend_always final_suspend() noexcept { return {}; }&#13;
&#13;
Task get_return_object() { &#13;
return std::coroutine_handle<promise_type>::from_promise(*this); &#13;
}&#13;
void return_void() {}&#13;
void unhandled_exception() {}&#13;
};&#13;
&#13;
Task(std::coroutine_handle<promise_type> handle): handle{handle}{}&#13;
&#13;
auto get_handle() { return handle; }&#13;
&#13;
std::coroutine_handle<promise_type> handle;&#13;
};&#13;
&#13;
using job = std::pair<int, std::coroutine_handle<>>;&#13;
&#13;
template <typename Updater = std::identity, // (1)&#13;
typename Comperator = std::ranges::less> &#13;
requires std::invocable<decltype(Updater()), int> && // (2)&#13;
std::predicate<decltype(Comperator()), job, job> &#13;
class Scheduler {&#13;
&#13;
std::priority_queue<job, std::vector<job>, Comperator> _prioTasks;&#13;
&#13;
public: &#13;
void emplace(int prio, std::coroutine_handle<> task) {&#13;
_prioTasks.push(std::make_pair(prio, task));&#13;
}&#13;
&#13;
void schedule() {&#13;
Updater upd = {}; // (3)&#13;
while(!_prioTasks.empty()) {&#13;
auto [prio, task] = _prioTasks.top();&#13;
_prioTasks.pop();&#13;
task.resume();&#13;
&#13;
if(!task.done()) { &#13;
_prioTasks.push(std::make_pair(upd(prio), task)); // (4)&#13;
}&#13;
else {&#13;
task.destroy();&#13;
}&#13;
}&#13;
}&#13;
&#13;
};&#13;
&#13;
&#13;
Task createTask(const std::string& name) {&#13;
std::cout << name << " startn";&#13;
co_await std::suspend_always();&#13;
for (int i = 0; i <= 3; ++i ) { &#13;
std::cout << name << " execute " << i << "n"; // (5)&#13;
co_await std::suspend_always();&#13;
}&#13;
co_await std::suspend_always();&#13;
std::cout << name << " finishn";&#13;
}&#13;
&#13;
&#13;
int main() {&#13;
&#13;
std::cout << 'n';&#13;
&#13;
Scheduler scheduler1; // (6)&#13;
&#13;
scheduler1.emplace(0, createTask("TaskA").get_handle());&#13;
scheduler1.emplace(1, createTask(" TaskB").get_handle());&#13;
scheduler1.emplace(2, createTask(" TaskC").get_handle());&#13;
&#13;
scheduler1.schedule();&#13;
&#13;
std::cout << 'n';&#13;
&#13;
Scheduler<decltype([](int a) { return a - 1; })> scheduler2; // (7)&#13;
&#13;
scheduler2.emplace(0, createTask("TaskA").get_handle());&#13;
scheduler2.emplace(1, createTask(" TaskB").get_handle());&#13;
scheduler2.emplace(2, createTask(" TaskC").get_handle());&#13;
&#13;
scheduler2.schedule();&#13;
&#13;
std::cout << 'n';&#13;
&#13;
}


Aldığım program çıktısı şuydu:








Christof Meerwald, Körfez İşbirliği Konseyi’nden farklı bir baskı aldı. Bu ipucu için teşekkürler İşte optimizasyonun etkin olduğu GCC’nin çıktısı.








Windows çıktısı da hatalıydı:








İşte hatanın önemli satırları:


Task createTask(const std::string& name) { // (1)&#13;
std::cout << name << " startn";&#13;
co_await std::suspend_always();&#13;
for (int i = 0; i <= 3; ++i ) { &#13;
std::cout << name << " execute " << i << "n"; &#13;
co_await std::suspend_always();&#13;
}&#13;
co_await std::suspend_always();&#13;
std::cout << name << " finishn";&#13;
}&#13;
&#13;
&#13;
int main() {&#13;
&#13;
std::cout << 'n';&#13;
&#13;
Scheduler scheduler1; &#13;
&#13;
scheduler1.emplace(0, createTask("TaskA").get_handle()); // (2)&#13;
scheduler1.emplace(1, createTask(" TaskB").get_handle()); // (3)&#13;
scheduler1.emplace(2, createTask(" TaskC").get_handle());// (4)&#13;
&#13;
scheduler1.schedule();&#13;
&#13;
std::cout << 'n';&#13;
&#13;
Scheduler<decltype([](int a) { return a - 1; })> scheduler2; &#13;
&#13;
scheduler2.emplace(0, createTask("TaskA").get_handle()); // (5)&#13;
scheduler2.emplace(1, createTask(" TaskB").get_handle()); // (6)&#13;
scheduler2.emplace(2, createTask(" TaskC").get_handle());// (7)&#13;
&#13;
scheduler2.schedule();&#13;
&#13;
std::cout << 'n';&#13;
&#13;
}


Koroutin createTask dizesini referans const lvalue(1) olarak alır, ancak argümanları “TaskA" - "TaskC” değerleri (2 – 7)’dir. Geçici bir değişkene referans kullanmak tanımsız bir davranıştır. Diğer zamanlayıcılar priority_SchedulerSimplified VE priority_queueSchedulerComparator “C++ Programlama Dili: Coroutines için Öncelik Zamanlayıcısı” ve “C++ Programlama Dili: Coroutines için Sofistike Öncelik Zamanlayıcısı” makalelerinde aynı sorun var.

Sorunu çözmek kolaydır. Veya koroutini alır createTask değere göre argümanları (Task createTask(std::string name)) veya argümanları lvalue olur. İşte (1) – (3)’teki ikinci yaklaşım:


// priority_queueSchedulerPriority.cpp&#13;
&#13;
#include <concepts>&#13;
#include <coroutine>&#13;
#include <functional>&#13;
#include <iostream>&#13;
#include <queue>&#13;
#include <utility>&#13;
&#13;
&#13;
struct Task {&#13;
&#13;
struct promise_type {&#13;
std::suspend_always initial_suspend() noexcept { return {}; }&#13;
std::suspend_always final_suspend() noexcept { return {}; }&#13;
&#13;
Task get_return_object() { &#13;
return std::coroutine_handle<promise_type>::from_promise(*this); &#13;
}&#13;
void return_void() {}&#13;
void unhandled_exception() {}&#13;
};&#13;
&#13;
Task(std::coroutine_handle<promise_type> handle): handle{handle}{}&#13;
&#13;
auto get_handle() { return handle; }&#13;
&#13;
std::coroutine_handle<promise_type> handle;&#13;
};&#13;
&#13;
using job = std::pair<int, std::coroutine_handle<>>;&#13;
&#13;
template <typename Updater = std::identity, &#13;
typename Comperator = std::ranges::less> &#13;
requires std::invocable<decltype(Updater()), int> && &#13;
std::predicate<decltype(Comperator()), job, job> &#13;
class Scheduler {&#13;
&#13;
std::priority_queue<job, std::vector<job>, Comperator> _prioTasks;&#13;
&#13;
public: &#13;
void emplace(int prio, std::coroutine_handle<> task) {&#13;
_prioTasks.push(std::make_pair(prio, task));&#13;
}&#13;
&#13;
void schedule() {&#13;
Updater upd = {}; &#13;
while(!_prioTasks.empty()) {&#13;
auto [prio, task] = _prioTasks.top();&#13;
_prioTasks.pop();&#13;
task.resume();&#13;
&#13;
if(!task.done()) { &#13;
_prioTasks.push(std::make_pair(upd(prio), task)); &#13;
}&#13;
else {&#13;
task.destroy();&#13;
}&#13;
}&#13;
}&#13;
&#13;
};&#13;
&#13;
&#13;
Task createTask(const std::string& name) {&#13;
std::cout << name << " startn";&#13;
co_await std::suspend_always();&#13;
for (int i = 0; i <= 3; ++i ) { &#13;
std::cout << name << " execute " << i << "n"; &#13;
co_await std::suspend_always();&#13;
}&#13;
co_await std::suspend_always();&#13;
std::cout << name << " finishn";&#13;
}&#13;
&#13;
&#13;
int main() {&#13;
&#13;
std::cout << 'n';&#13;
&#13;
std::string taskA = "TaskA"; // (1)&#13;
std::string taskB = " TaskB"; // (2)&#13;
std::string taskC = " TaskC"; // (3)&#13;
&#13;
Scheduler scheduler1; &#13;
&#13;
scheduler1.emplace(0, createTask(taskA).get_handle());&#13;
scheduler1.emplace(1, createTask(taskB).get_handle());&#13;
scheduler1.emplace(2, createTask(taskC).get_handle());&#13;
&#13;
scheduler1.schedule();&#13;
&#13;
std::cout << 'n';&#13;
&#13;
Scheduler<decltype([](int a) { return a - 1; })> scheduler2; &#13;
&#13;
scheduler2.emplace(0, createTask(taskA).get_handle());&#13;
scheduler2.emplace(1, createTask(taskB).get_handle());&#13;
scheduler2.emplace(2, createTask(taskC).get_handle());&#13;
&#13;
scheduler2.schedule();&#13;
&#13;
std::cout << 'n';&#13;
&#13;
}


Sıradaki ne?


Eşyordamlar, eşzamansız kod yazmanın sezgisel bir yolunu sağlar. Bir sonraki yazım Ljubic Damir’in ortak rutin tabanlı tek üretici, tek tüketicili iş akışını tanıtan bir konuk gönderisi olacak.


(kendim)



Haberin Sonu
 
Üst