Visual walkthrough — std - promise and std - future
5.2.28 · D2· Coding › C++ Programming › std - promise and std - future
Step 1 — Do threads jo baat nahi kar sakte
KYA. Socho do independent workers ek saath chal rahe hain. Ek thread bas "ek line of code jo apne aap execute ho rahi hai" hai, jaise ek doosra cursor instructions ke through step kar raha ho jabki pehla chalta rahe (dekho std - thread). Inhe do parallel lanes ki tarah draw karo.
KYUN. promise/future ka poora karan yahi hai ki ye do lanes alag hain. Thread A (consumer) ek number chahta hai. Thread B (producer) woh hai jo actually ise compute kar raha hai. Lekin B apna answer A ko simply return nahi kar sakta — ek thread function ki return value kahin nahi jaati jahan creator padh sake.
PICTURE. Do lanes side by side, unke beech ek bada gap, aur ek red "?" jo dikhata hai ki A ke paas B ka result access karne ka koi tarika nahi hai.
Step 2 — Gap mein ek mailbox rakho: shared state
KYA. Hum ek chhoti si box beech mein rakhte hain, dono lanes ke liye accessible. Parent ise shared state kehta hai. Yeh heap par memory ka ek chunk hai jisme chaar cheezein hain:
value— jahan number eventually baithega.ex— ek alternative slot agar number ki jagah kuch galat ho gaya.ready— ekbool(true/false) jiska matlab hai "kya box fill ho gaya hai?".m,cv— do synchronization tools jo hum Steps 5–6 mein earn karte hain.
KYUN. Ek single shared box ka matlab hai dono threads same memory ko point karte hain, isliye ek ke dwara likhi gayi value doosre ko visible hoti hai. Information red gap ko cross karne ka yahi ek tarika hai.
PICTURE. Box gap mein baitha hai, dono lanes usme arrow draw kar rahi hain. ready false shuru hota hai (empty box).
Step 3 — Ek box par do handles
KYA. Hum box ko directly kabhi nahi chhoote. Balki har thread ko ek handle milta hai:
Tum promise banate ho, phir usse matching future maango:
get_future() ek baar call karo; doosri call future_error throw karegi.
KYUN. Write-only aur read-only handles mein split karna ek unidirectional flow enforce karta hai: producer fill karta hai, consumer padh ta hai. Koi accidentally dono nahi kar sakta.
PICTURE. Box unchanged; ek blue promise handle left lane se clipped, ek orange future handle right lane se clipped, dono same box se wired.
Step 4 — Consumer jaldi aa jaata hai aur wait karna padta hai
KYA. Real timing: thread A aksar fut.get() tak pahuncha pehle thread B ne kuch compute kiya ho. Box abhi bhi empty hai (ready == false). A ko kya karna chahiye?
KYUN. Do bure options, ek acha:
- Busy-spin —
readyko lakho baar check karte rahna. Yeh ek CPU core ka 100% kuch nahi karte hue burn karta hai. Rejected. - Hamesha ke liye so jaana — lekin phir A ko kaun jagayega jab value aaye? Bina kisi tool ke unsolved.
- Soye, aur write par jagaya jaaye — sahi jawab. Yahi Step 6 ke tool ka kaam hai.
PICTURE. A box tak pahuncha, ready = false dekha, aur ek clock icon dikhata hai ki yeh park ho raha hai (so raha hai) spin karne ki jagah.
Step 5 — Mutex kyun: do haath ek saath na aayein
KYA. Producer B value likhta hai aur ready flip karta hai; consumer A inhe padhta hai. Agar dono same memory ko ek hi instant mein chhoote, yeh ek data race hai — undefined behavior (program garbage print kar sakta hai ya crash ho sakta hai). Fix hai mutex m: ek lock jo ek waqt mein sirf ek thread hold kar sakta hai (dekho std - mutex and lock_guard).
KYUN. Ek mutex access ko serialize karta hai: jab B m hold karta hai likhne ke liye, A half-written data nahi padh sakta, aur vice-versa. value + ready ki write A ke liye atomic ban jaati hai.
PICTURE. Box par ek padlock. B ke paas key hai (writing); A locked door ke bahar wait kar raha hai.
Step 6 — Condition_variable kyun: abhi so jao, write par jago
KYA. Ek condition_variable cv (dekho std - condition_variable) ek thread ko soone deta hai mutex release karte hue, aur doosre thread ke dwara notify call karne par jagaya jaata hai. Consumer is tarah wait karta hai:
lk— lock;waitsoते waqt ise release karta hai (taaki producer ise grab kar sake) aur wake hone par re-lock karta hai.- predicate
[]{return ready;}— ek chhota sa yes/no test jo har wake par re-check hota hai.
Predicate kyun, bare wait(lk) nahi? Figure mein do reasons hain:
- Spurious wakeup — ek
cvbina kisi notify ke wake ho sakta hai. Predicate kehta hai "readyabhi bhi false → wapas so jao." Koi false start nahi. - Set-before-wait race — agar B ne A ke
waittak pahunchne se pehle box fill kar diya,readyalready true hai, isliyewaitbina soye instantly return karta hai. Koi missed wakeup nahi.
PICTURE. Ek timeline: A wait call karta hai, lock release karta hai, so jaata hai. B lock ke andar likhta hai aur notify_all() call karta hai. A jaag ta hai, predicate re-check karta hai (ready == true), aage badh ta hai.
Step 7 — Write: set_value box fill karta hai aur ghanti bajata hai
KYA. Producer khatam karta hai aur karta hai:
{
std::lock_guard<std::mutex> lk(state->m);
state->value = v; // (1) result daalo
state->ready = true; // (2) flag flip karo
} // (3) block khatam hote hi unlock
state->cv.notify_all(); // (4) ghanti bajao -> sleeper ko jagaoTerm by term: (1) actual data store karta hai, (2) kisi bhi reader ko batata hai "data present hai", (3) mutex release karta hai taaki reader ise lock kar sake, (4) notify_all A ki cv.wait ko jagata hai.
KYUN yeh order. Value flag se pehle: agar hum pehle ready set kar den aur interrupt ho jaayein, A wake ho sakta hai aur empty value padh sakta hai. Flag lock ke andar guarantee karta hai ki A ek fully-written box dekhe.
PICTURE. Box ab filled (green value), ready true ho gaya, ek ghanti sote hue consumer ki taraf arrow ring kar rahi hai.
Step 8 — Read: get() value ko ek baar move karta hai bahar
KYA. Consumer, ab jaga hua predicate true ke saath, chalata hai:
if (state->ex) std::rethrow_exception(state->ex); // exception path (Step 9)
return std::move(state->value); // value pathstd::move(state->value) result ko move karta hai bahar (bade objects ke liye sasta) aur box ko drained chhod deta hai. get() ke baad, future invalid hai: doosra get() future_error(no_state) throw karta hai. Yeh one-shot guarantee hai.
KYUN move, copy nahi? Box value ko own karta hai aur ab isse kaam nahi; move karna ek possibly-large object ko copy karne se bachata hai aur channel ko consumed mark karta hai. State ko fut.valid() se check karo — get() se pehle true, baad mein false.
PICTURE. Value A ke haath mein orange arrow ke saath flow kar rahi hai; box ab empty hai aur valid() 1 → 0 ho raha hai.
Step 9 — Edge cases: exception, aur broken promise
KYA. Do tarike jisme happy path fail hoti hai, dono usi box ke dwara handle hote hain:
-
Exception transport. Value ki jagah, producer ek error bhejta hai:
catch (...) { p.set_exception(std::current_exception()); }valueki jagahexslot fill hota hai. Reading side par,get()dekhaexset hai aur ise consumer ke thread mein re-throw karta hai (dekho Exception handling in C++). Thread B mein paida hua error ab thread A mein catchable hai. -
Broken promise (degenerate case). Agar producer bina
set_value/set_exceptioncall kiye kabhi destroy ho jaata hai, shared state khud ko broken mark kar leta hai. Phirget()future_error(broken_promise)throw karta hai — isliye ek stuck consumer hamesha ke liye hang karne ki jagah error ke saath wakeup hota hai.
KYUN yeh matter karte hain. Bina (1) ke B mein throw ki gayi exception std::terminate call karegi aur poora program maar degi. Bina (2) ke ek forgotten set_value A ko hamesha ke liye freeze kar dega. Box har exit path cover karta hai: value, error, ya abandonment.
PICTURE. Box se teen-taraf ka fork: green "value" arrow, orange "exception re-thrown" arrow, red "broken_promise" arrow.
Ek-picture summary
Upar sab kuch ek single frame mein compress kiya gaya hai: producer lane left par promise hold karte hue, consumer lane right par future hold karte hue, shared-state box red gap ko bridge karte hue, aur box se nikal rahe teen possible outcomes.
Recall Feynman retelling — ise plain words mein wapas bolo
Do workers alag lanes mein chalte hain aur cheezein seedha ek doosre ko nahi de sakte. Isliye hum gap mein ek chhoti si box rakhte hain jise dono reach kar sakein. Promise write-handle hai, future read-handle hai — hum pehle read-handle lete hain, phir write-handle doosre worker ko bhejte hain. Reader jaldi aa jaata hai, ek ready flag check karta hai, aur — bekaar spin karne ki jagah — so jaata hai ek condition variable use karke jo use box fill hote hi jagayegi. Ek mutex pakka karta hai ki koi half-written box na padhe. Producer, lock ke andar, value dalta hai, ready ko true karta hai, aur ghanti bajata hai. Soye hue wala jaagtaa hai, flag double-check karta hai (taaki ek fake wakeup ya early write use fool na kare), aur value ko move karta hai bahar — exactly once. Agar kuch galat ho gaya, producer exception box mein bharta hai, aur reader ka get() use wahan re-throw karta hai. Aur agar producer box fill kiye bina gaayab ho jaata hai, reader hamesha ke liye hang karne ki jagah broken_promise error pata hai. Yahi poori machine hai.
Recall Quick self-test
Predicate ke saath kyun soye, bare wait se kyun nahi? ::: Spurious wakeups aur set-before-wait race se bachne ke liye — predicate har wake par ready re-check karta hai.
Value ko ready flip karne se pehle kyun store karo? ::: Taaki jo reader ready==true dekhe, use guarantee ho ki fully-written value dikh rahi hai, kabhi half-written nahi.
Box khali karne ke teen tarike kya hain? ::: Ek value (set_value), ek exception (set_exception, get ke dwara re-thrown), ya abandonment (broken_promise).
Related tools: std - async and std - launch aur std - packaged_task tumhare liye isi box ko wrap karte hain; std - shared_future kai readers ko ek box share karne deta hai.