asynchronous programming (tsz. asynchronous programmings)
A modern C++ (főleg a C++11-től kezdve) számos eszközt kínál az aszinkron programozáshoz:
A legegyszerűbb formája az aszinkron programozásnak a std::thread.
Egy példát nézzünk:
#include <iostream>
#include <thread>
void hosszúFeladat() {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "Feladat kész\n";
}
int main() {
std::thread t(hosszúFeladat);
std::cout << "Főszál dolgozik közben...\n";
t.join(); // Megvárjuk a szál végét
}
A std::thread
előnye, hogy teljes kontrollunk van a szál felett, de figyelni kell arra, hogy minden szálat join
-oljunk, vagy detach
-eljünk.
A std::async segítségével szinte egy sorban elindíthatunk egy aszinkron műveletet, amelyhez később egy future objektumon keresztül férhetünk hozzá.
#include <iostream>
#include <future>
#include <chrono>
int számol() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42;
}
int main() {
std::future<int> eredmény = std::async(std::launch::async, számol);
std::cout << "Várok az eredményre...\n";
int valasz = eredmény.get(); // Itt blokkol, amíg kész
std::cout << "Eredmény: " << valasz << "\n";
}
Az async
kényelmes, mert nem kell manuálisan kezelni a szálakat, viszont nem ad teljes kontrollt a futtatás ütemezésére.
Ha egy szál valamikor a jövőben szeretne visszaadni egy értéket, akkor használhatunk std::promise
-t és hozzá tartozó std::future
-t.
#include <iostream>
#include <thread>
#include <future>
void dolgozik(std::promise<int> &&ígéret) {
std::this_thread::sleep_for(std::chrono::seconds(2));
ígéret.set_value(123);
}
int main() {
std::promise<int> ígéret;
std::future<int> jövő = ígéret.get_future();
std::thread t(dolgozik, std::move(ígéret));
std::cout << "Várakozás az eredményre...\n";
std::cout << "Kaptuk: " << jövő.get() << std::endl;
t.join();
}
Ez akkor hasznos, ha nem a std::async
kényelmére van szükség, hanem explicit szálak és értékek áramoltatása történik.
A std::packaged_task
-kal becsomagolhatunk egy tetszőleges függvényt, amely később futtatható és a visszatérési értéke egy future
-ön keresztül lekérdezhető.
#include <iostream>
#include <future>
#include <thread>
int számol() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 5;
}
int main() {
std::packaged_task<int()> feladat(számol);
std::future<int> eredmény = feladat.get_future();
std::thread(std::move(feladat)).detach();
std::cout << "Az eredmény: " << eredmény.get() << "\n";
}
A packaged_task
egy alacsonyabb szintű eszköz, mint az async
, de jól jön, ha dinamikusan szeretnénk feladatokat kezelni.
A std::thread
és az async
is jó, de sok szál létrehozása és megsemmisítése drága művelet lehet. A thread pool egy fix számú szállal dolgozik, és csak feladatokat oszt ki nekik. Ez hatékonyabb, de bonyolultabb megvalósítást igényel. Erre használható például a Boost::asio vagy más külső könyvtárak.
A korutinok lehetővé teszik, hogy a függvények szinte „szüneteltethetőek” legyenek. Ez hasonlít a Pythonban ismert async/await
szerkezetekhez.
A szintaxis elég összetett, de a legnagyobb előnye, hogy nem kell explicit szálakat vagy future-öket kezelni, a vezérlés és az állapot automatikusan történik.
#include <iostream>
#include <coroutine>
struct EgyediFuture {
struct promise_type {
EgyediFuture get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
EgyediFuture példa() {
std::cout << "Előtte\n";
co_await std::suspend_always{};
std::cout << "Utána\n";
}
int main() {
auto fut = példa();
std::cout << "Főprogram vége\n";
}
Ez egy nagyon alap korutin példa, a valóságban ennél sokkal bonyolultabb keretrendszerek épülhetnek rá.
std::async
-ot.std::mutex
, std::lock_guard
vagy std::scoped_lock
eszközöket.
Az aszinkron programozás lehetővé teszi, hogy a programok gyorsabban reagáljanak és hatékonyabban használják a számítási erőforrásokat. A C++ eszköztára ebben a témában folyamatosan fejlődött, a legegyszerűbb std::thread
-től kezdve a modern std::async
-on át a korutinokig. Minden eszköznek megvan a maga helye: az egyszerű feladatokhoz érdemes a magas szintű absztrakciókat használni, míg összetettebb rendszerekhez a finomhangolhatóbb eszközök alkalmasak. Bár az aszinkron programozás bonyolult lehet, ha jól csináljuk, komoly teljesítménynyereséget és jobb felhasználói élményt eredményez.