Exercises — std - promise and std - future
5.2.28 · D4· Coding › C++ Programming › std - promise and std - future
Yeh page parent topic ke liye ek drill sheet hai. Agar koi term unfamiliar lage, toh woh topic usse zero se build karta hai pehle.
Level 1 — Recognition
Yeh test karta hai ki kya tum sahi piece ko naam de aur point kar sakte ho.
L1.1
One-shot mailbox mein writing end kaun sa object hai aur reading end kaun sa?
Recall Solution
- Writing end (producer):
std::promise<T>— tum is parset_value/set_exceptioncall karte ho. (Yeh page ke top-of-page figure mein left side wala pen hai.) - Reading end (consumer):
std::future<T>— tum is parget()/wait()call karte ho. (Yeh us figure mein right side wali key hai.)
Dono ek hi shared state box ki taraf point karte hain. Mnemonic: ek promise woh hoti hai jo tum karte ho (likhte ho); ek future woh hoti hai jiska tum intezaar karte ho (padhte ho).
L1.2
Woh single call fill karo jo dono ends ko link karta hai:
std::promise<int> p;
std::future<int> f = p.__________;Recall Solution
std::future<int> f = p.get_future();get_future() tumhe usi shared state box ki reading key deta hai (top-of-page figure mein dashed white arrow). Isse exactly ek baar call karo — doosri call std::future_error throw karegi, kyunki ek single writer ek box ke liye do independent reading ends distribute nahi kar sakta.
L1.3
Har line ke liye batao ki woh producer ki hai ya consumer ki:
set_value · get · set_exception · wait · get_future
Recall Solution
Producer (promise) ::: set_value, set_exception, get_future
Consumer (future) ::: get, wait
(get_future promise par rehta hai lekin consumer object produce karta hai.)
Level 2 — Application
Ab tum API ko chote complete snippets mein use karte ho.
L2.1
Ek worker 9 * 9 compute karta hai. worker ko complete karo taaki future ko 81 mile.
void worker(std::promise<int> p) {
int r = 9 * 9;
// ???
}Recall Solution
void worker(std::promise<int> p) {
int r = 9 * 9;
p.set_value(r); // box mein 81 store karo, ready mark karo, waiter ko jagao
}Yeh top-of-page figure mein pen ka box mein likhna hai. Doosri side par fut.get() phir 81 return karega.
L2.2
Inhe chaar lines ko sahi order mein arrange karo taaki main compile aur run ho. t.join() inme se ek nahi hai — assume karo ki yeh already tumhari chaar lines mein se last ke baad wali line par likha hai (tum sirf A–D ko reorder karo):
A: std::thread t(worker, std::move(prom));
B: std::promise<int> prom;
C: std::future<int> fut = prom.get_future();
D: int x = fut.get();
Recall Solution
Chaar lines ka sahi order: B, C, A, D (saath mein pehle se diya t.join(); D ke baad aata hai).
std::promise<int> prom; // B: promise banao (box create hota hai)
std::future<int> fut = prom.get_future();// C: move se pehle reading key lo
std::thread t(worker, std::move(prom)); // A: move-only, promise thread ko do
int x = fut.get(); // D: set_value hone tak block karo -> value
t.join(); // (diya hua, A–D mein se nahi)C pehle A se kyun: std::move(prom) promise ki box ki ownership thread mein transfer kar deta hai; local prom khaali reh jaata hai (koi shared state nahi). Agar tum phir prom.get_future() call karo, toh key dene ke liye koi box hi nahi hai, isliye future_error(no_state) throw hoga. Move se pehle key lene se woh tab capture hoti hai jab local promise abhi bhi box ko own karti hai. Line D top-of-page figure mein key ka box se padhna hai.
L2.3
Yeh code runtime par deadlock ya crash karta hai. Jo value print hogi ya throw hogi, woh kya hai aur kyun?
std::promise<int> prom;
auto fut = prom.get_future();
std::thread t([p = std::move(prom)]() mutable {
/* kuch bhi set karna bhool gaye */
});
t.join();
int x = fut.get(); // yahan kya hoga?Recall Solution
fut.get() broken_promise error code ke saath std::future_error throw karega.
Lambda ne promise ko move se capture kiya aur use set_value/set_exception ke bina destroy ho jaane diya. Promise ka destructor, yeh dekh kar ki box kabhi fill nahi hua, box ko ready mark karta hai lekin broken_promise error ke saath, phir waiters ko jagata hai — isliye wait kar raha get() koi value deliver nahi kar sakta aur hamesha ke liye hang hone ki jagah re-throw karta hai.
Fix: har path par promise set karo — jaise lambda ke andar p.set_value(0);.
Level 3 — Analysis
Behaviour, ordering, aur correctness ke baare mein reason karo.
L3.1
Explain karo ki future::get() sirf ek baar kyun call kiya ja sakta hai, aur doosri call kya karti hai. Phir "many readers" case ke liye standard tool batao.
Recall Solution
get() stored value ko move out karta hai (dekho Move semantics and std::move): return hone ke baad, box is future ke liye ek value hold nahi karta, aur fut.valid() false ho jaata hai. Isliye doosri get() kuch return nahi kar sakti aur std::future_error(no_state) throw karti hai.
Multiple consumers ke liye, std::shared_future use karo (dekho std::shared_future): iska get() ek const reference return karta hai (move out ki jagah copy out karta hai), isliye isе kai copies dwara baar baar call kiya ja sakta hai.
L3.2
Hand-built shared state mein, reader pehle std::unique_lock se mutex lock karta hai, phir condition variable par wait karta hai. Wait
cv.wait(lk, []{ return ready; }) likha jaata hai, na ki plain cv.wait(lk). Yeh kyun? Predicate jo do alag problems solve karta hai, unhe naam do.
Recall Solution
Pehle, lk kya hai? Yeh lock object hai jo mutex hold karta hai:
std::unique_lock<std::mutex> lk(m); // lk mutex m par lock own karta hai
cv.wait(lk, []{ return ready; }); // cv atomically lk ko sleeping mein release karta hai,
// wake par re-acquire karta hai, phir ready check karta haicv.wait ko unique_lock chahiye (lock_guard nahi) exactly isliye kyunki jab thread sota hai tab use mutex unlock karna hota hai aur wake par re-lock karna hota hai — dekho std::mutex and lock_guard.
Predicate jo do problems solve karta hai (dono std::condition_variable se):
- Spurious wakeups — ek
condition_variablebina kisinotifykewaitse return kar sakta hai. Predicatereadyko dobara check karta hai aur agar woh abhi bhi false hai toh wapis so jaata hai. - Lost wakeup / set-before-wait race — agar
set_valuereader kewaittak pahunchne se pehle run ho, tohreadyalreadytruehai, isliye predicate form immediately return karta hai instead of hamesha ke liye block hone ke, aisa wait karte hue jo already fire ho chukanotifyka.
Plain cv.wait(lk) dono mein se koi bhi handle nahi karta.
L3.3
Teen marked points par fut.valid() ki states trace karo:
std::promise<std::string> prom;
auto fut = prom.get_future(); // (A)
prom.set_value("hi"); // (B)
std::string s = fut.get(); // (C)Recall Solution
(A) get_future() ke baad ::: true — ek shared state ke saath associated hai
(B) set_value ke baad ::: true — value stored hai, lekin future abhi consume nahi hua
(C) get() ke baad ::: false — value move out ho gayi, future invalidate ho gaya
Toh sequence hai true, true, false. Value set karna future ko invalidate nahi karta; sirf use consume karna karta hai.
Level 4 — Synthesis
promise/future ko neighbouring tools ke saath combine karo.
L4.1
Is raw-thread pattern ko std::promise/std::future use karke rewrite karo taaki main ko result mile bina apne global flag, mutex, ya condition_variable ke:
int shared_result;
bool ready = false; // haath se bana sync — yeh sab replace karo
std::mutex m; std::condition_variable cv;Recall Solution
#include <future>
#include <thread>
#include <iostream>
void worker(std::promise<int> p) {
p.set_value(123); // ek call storage + notify dono package karta hai
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(worker, std::move(prom));
std::cout << fut.get() << "\n"; // -> 123
t.join();
}Single promise/future pair mutex, std::condition_variable, ready flag, aur shared variable — sab shared state ke andar sahi tarike se bundle hoke — replace kar deta hai.
L4.2
Thread boundary ke across ek exception bhejo. Worker std::out_of_range("bad index") throw karta hai; main ko ise get() par catch karke caught: bad index print karna hai.
Recall Solution
void worker(std::promise<int> p) {
try {
throw std::out_of_range("bad index");
} catch (...) {
p.set_exception(std::current_exception()); // capture & ship
}
}
int main() {
std::promise<int> prom;
auto fut = prom.get_future();
std::thread t(worker, std::move(prom));
try {
int x = fut.get(); // yahan re-throw hota hai, IS thread mein
std::cout << x;
} catch (const std::exception& e) {
std::cout << "caught: " << e.what() << "\n"; // -> caught: bad index
}
t.join();
}Dekho Exception handling in C++. Iske bina, thread function se escape hoti exception std::terminate call karti hai.
L4.3
std::async tumhe promise ko kabhi touch kiye bina future deta hai. L4.1 ko std::async and std::launch use karke rewrite karo taaki koi explicit promise, thread, ya join nazar na aaye.
Recall Solution
#include <future>
#include <iostream>
int main() {
std::future<int> fut = std::async(std::launch::async,
[]{ return 123; });
std::cout << fut.get() << "\n"; // -> 123
}std::async internally promise/shared-state create karta hai, callable run karta hai, tumhare liye value (ya exception) set karta hai, aur future wapis deta hai.
Destructor nuance ke baare mein savdhani: std::async dwara return kiya gaya future special hota hai — jab woh destroy hota hai tab woh task finish hone tak block karega, lekin sirf tab jab task std::launch::async ke saath launch hua ho aur abhi bhi run ho raha ho. std::launch::deferred ke saath callable shuru nahi hua hota, aur deferred task sirf tab run hota hai jab tum explicitly get() ya wait() call karo — future ka destructor use run nahi karta; agar tum kabhi get()/wait() call nahi karte, toh callable simply kabhi execute nahi hota. Agar tum launch policy omit karo (std::async(f)), toh implementation koi bhi policy choose kar sakti hai, isliye tum na blocking na deferred behaviour par rely kar sakte ho. Sirf std::launch::async "join on destruction" behaviour guarantee karta hai jaise upar use kiya gaya. (Plain std::promise se obtained futures destruction par block nahi karte.)
Level 5 — Mastery
Machinery ko khud build karo aur us par reason karo.
L5.1
Ek minimal MiniPromise<T> / MiniFuture<T> pair implement karo jo ek haath-bane state ko share kare, set_value, set_exception, ek blocking get(), aur write-once enforcement support kare. Woh members list karo jo shared state mein hone chahiye aur har ek kyun exist karta hai.
Recall Solution
Members aur unke kaam (yeh exactly wahi box hai jo page ke top par draw kiya gaya hai):
T value— successful result ke liye storage (figure mein "value" inner slot).std::exception_ptr ex— ek alternative slot taaki ek throw ki gayi exception value ki jagah ship ki ja sake.bool ready = false— reader-facing flag: "kya box fill ho gaya?" Consumer kacv.waittab tak sota rehta hai jab tak yehtruenahi ho jaata ("ready" inner slot).bool written = false— write-once enforcement ke liye writer-facing guard: "kya kisi producer ne alreadyset_value/set_exceptioncall kar li?" Hume isereadyse alag chahiye kyunki ekset_*call ko doosri write attempt detect karni hoti hai (aur throw karna hota hai) —readyakela "filled, padhne ka wait" aur "koi doosri baar likhne ki koshish kar raha hai" ke beech distinguish nahi kar sakta. Realstd::promisemein yahi guardpromise_already_satisfiedraise karta hai.std::mutex m—value/ex/ready/writtenko threads ke across data race se protect karta hai ("mutex" inner slot).std::condition_variable cv— reader ko busy-spin karne ki jagah sleep karne aur jagaye jaane deta hai ("condition_variable" inner slot).
#include <mutex>
#include <condition_variable>
#include <exception>
#include <memory>
#include <stdexcept>
template<class T>
struct State {
std::mutex m;
std::condition_variable cv;
bool ready = false; // reader flag: kya box fill ho gaya?
bool written = false; // writer guard: kya koi bhi set_* already hua?
T value{};
std::exception_ptr ex;
};
template<class T> struct MiniFuture; // forward declaration
template<class T>
struct MiniPromise {
std::shared_ptr<State<T>> s = std::make_shared<State<T>>();
MiniFuture<T> get_future(); // neeche define hua, MiniFuture ke baad
void set_value(T v) {
{ std::lock_guard<std::mutex> lk(s->m);
if (s->written) throw std::logic_error("already satisfied");
s->value = std::move(v);
s->written = true; // write-once guard
s->ready = true; // reader ko jagao
}
s->cv.notify_all();
}
void set_exception(std::exception_ptr e) {
{ std::lock_guard<std::mutex> lk(s->m);
if (s->written) throw std::logic_error("already satisfied");
s->ex = e;
s->written = true;
s->ready = true;
}
s->cv.notify_all();
}
};
template<class T>
struct MiniFuture {
std::shared_ptr<State<T>> s;
T get() {
std::unique_lock<std::mutex> lk(s->m); // unique_lock: wait ise unlock kar sakta hai
s->cv.wait(lk, [&]{ return s->ready; }); // fill hone tak so jao
if (s->ex) std::rethrow_exception(s->ex);// exception transport
return std::move(s->value); // value move out karo
}
};
// ab MiniFuture complete hai, isliye get_future() define kar sakte hain:
template<class T>
MiniFuture<T> MiniPromise<T>::get_future() {
return MiniFuture<T>{ s }; // usi shared state ko future ko do
}Dono flags alag kaam karte hain: written producer side par write-once enforce karta hai (doosre set_* par throw karta hai), jabki ready woh predicate hai jis par consumer wait karta hai. ex slot exception transport provide karta hai, aur get_future() simply shared_ptr copy karta hai taaki promise aur future ek State ko co-own karein — yahi kaam real std::promise/std::future karta hai.
L5.2
Ek thread pool 3 independent tasks run karta hai jo ints 10, 20, 30 return karte hain. Saare results std::vector<std::future<int>> use karke collect karo aur unka sum nikalo. Sum kya hai, aur push_back ka order correctness ke liye irrelevant kyun hai?
Recall Solution
std::vector<std::future<int>> futs;
futs.push_back(std::async(std::launch::async, []{ return 10; }));
futs.push_back(std::async(std::launch::async, []{ return 20; }));
futs.push_back(std::async(std::launch::async, []{ return 30; }));
int sum = 0;
for (auto& f : futs) sum += f.get(); // har get() APNE task ka wait karta hai
// sum == 60Sum 60 hai. Har get() apne khud ke shared state ke liye independently wait karta hai, isliye tasks jo bhi order mein finish hote hain, loop simply har ek par block karta hai aur use add karta hai. Addition commutative hai, isliye total order-independent hai.
L5.3
Shared state ki ownership ke terms mein explain karo ki unfulfilled promise destroy hone par future ka get() broken_promise throw kyun karta hai hamesha ke liye block hone ki jagah. Kaunsa design choice yahan deadlock impossible banata hai?
Recall Solution
Promise aur future box ko co-own karte hain (ek shared control block ke zariye). Jab promise destroy hota hai, iska destructor check karta hai: "kya maine kabhi koi value/exception set ki?" Agar nahi, toh woh box mein ek broken_promise error store karta hai aur use ready mark karta hai — phir waiters ko jagata hai.
Isliye wait kar raha get() ready == true dekhta hai, stored future_error(broken_promise) paata hai, aur ise re-throw karta hai. Key design choice: "promise destruction hamesha box resolve karta hai (value, exception, ya broken-promise error ke saath)", jo guarantee karta hai ki future kabhi aisi promise par wait nahi kar sakti jo kabhi jawab nahi degi — koi deadlock nahi.
Recall Quick self-check summary
- Writing end = promise, reading end = future; dono ek shared state box ki taraf point karte hain.
get_future()std::move(prom)se pehle.- Exactly ek write (
set_valueyaset_exception). - Exactly ek read (
get()), jo future ko invalidate karta hai. - Unset-then-destroyed promise →
get()parbroken_promise.