5.2.28 · D3 · HinglishC++ Programming

Worked examplesstd - promise and std - future

3,579 words16 min read↑ Read in English

5.2.28 · D3 · Coding › C++ Programming › std - promise and std - future

Shuru karne se pehle, poori cheez ka ek picture taaki baad ke har example isi par point kar sake. Ek promise aur ek future ek single heap block share karte hain — the shared state.

Figure — std - promise and std - future

Figure ko left se right padhо. Magenta box left mein std::promise hai — writing end. Violet box right mein std::future hai — reading end. Top par dashed navy arrow prom.get_future() hai: ek baar call kiya jaata hai, aur reading end ko shared block ka link deta hai. Orange box bottom mein shared state hai, jo heap par rehta hai, aur chaar cheezein hold karta hai: value ya exception ke liye ek slot, ek ready flag, aur do synchronization tools — ek mutex (ise m kaho, ek lock jo ensure karta hai ki sirf ek thread box ko ek waqt touch kare) aur ek condition variable (ise cv kaho, ek "waiting bench" jis par ek thread so sakta hai jab tak use jagaya na jaaye). Magenta arrow set_value / set_exception hai jo box mein ek baar likhta hai; violet arrow get() hai jo isse ek baar padhta hai. Niche ke har example mein in do arrows ke along ek alag journey hai.

Us picture ke do identifiers baar baar aate rahenge, isliye hum inhe abhi, ek baar, naam dete hain:

  • ==m== — the mutex (from mutex): ek lock jo guarantee karta hai ki sirf ek thread box ko kisi bhi waqt read ya write kare, taaki koi data race na ho.
  • ==cv== — the condition variable (from condition_variable): woh bench jis par ek thread get() ke andar sota hai jab tak set_value use jagaaye. Ek ==lk== bas m par ek unique_lock hai — woh handle jo ek thread rakhta hai jab woh cv par sota hai, sone ke dauran automatically release hota hai aur jagane par re-acquire hota hai.

The scenario matrix

promise/future ka har use inhi cells mein se ek mein fall karta hai. Columns ko socho "kya likha gaya" aur rows ko socho "timing / kaun padhta hai".

Cell Situation Future ka get() kya karta hai
A. Value, reader waits Producer value set karta hai; consumer pehle se get() mein block hai Sota hai, jagaya jaata hai, value return karta hai
B. Value, value-set-first Producer value pehle set karta hai, consumer baad mein get() call karta hai Instantly return karta hai — koi sleep nahi
C. Exception transported Producer ek exception store karta hai get() consumer thread mein use re-throw karta hai
D. Broken promise Producer kuch set kiye bina destroy ho jaata hai get() future_error(broken_promise) throw karta hai
E. One-shot violation Consumer get() do baar call karta hai, ya get_future() do baar future_error(no_state / future_already_retrieved) throw karta hai
F. Zero / degenerate payload promise<void> — ek pure "done" signal, koi data nahi get() kuch return nahi karta, bas unblock karta hai
G. Multiple readers Kai threads ko ek hi result dekhna hai Plain future fail karta hai (one-shot); [[std - shared_future
H. Limiting: timeout Consumer forever wait nahi kar sakta wait_for ek status return karta hai, get() ready hone tak avoid kiya jaata hai
I. Real-world word problem Do heavy computations parallel mein chalti hain, main thread dono sum karta hai Do mailboxes, do get()s
J. Exam twist Chained: packaged_task ek future feed karta hai; promise kahan hai? Task hi writing end hai

Niche ke nau examples har cell A–J ko hit karte hain. Har ek apni cell ka naam leta hai.


Example 1 — Cell A: value, reader pehle se wait kar raha hai

Forecast: printed number guess karo, aur guess karo ki main soya ya busy-spin kiya.

#include <future>
#include <thread>
#include <chrono>
#include <iostream>
 
void worker(std::promise<int> p) {
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    p.set_value(6 * 7);                 // Step 3
}
 
int main() {
    std::promise<int> prom;
    std::future<int>  fut = prom.get_future();   // Step 1
    std::thread t(worker, std::move(prom));      // Step 2
    std::cout << fut.get() << "\n";              // Step 4
    t.join();
}
  1. Move se pehle get_future(). Yeh step kyun? Jab hum step 2 mein std::move(prom) karte hain to local prom empty ho jaata hai; tab use future ke liye poochna throw karega. Pehle reading end grab karo.
  2. Thread mein std::move(prom). Yeh step kyun? std::promise move-only hai (dekho Move semantics and std::move); copy karne se ek shared state ke liye do writers ban jaate.
  3. Sleep ke baad set_value(6 * 7). Yeh step kyun? Yeh value likhta hai, ready flag flip karta hai, aur cv par kisi bhi waiter ko jagaata hai — bilkul parent ke hand-built version mein set_value block jaisa.
  4. fut.get(). Yeh step kyun? main 50 ms khatam hone se pehle yahan pahunch jaata hai, isliye shared state ready nahi hai. get() main ko condition variable cv par so deta hai (busy loop nahi), aur CPU free rehta hai jab tak step 3 use jagaaye.

Verify: printed value hai. Timing sanity: main ~50 ms se pehle print nahi kar sakta, jo prove karta hai ki woh genuinely blocked tha aur stale zero nahi padh raha tha.


Example 2 — Cell B: reader ke wait karne se pehle value set ho jaati hai

Forecast: kya get() instantly return karega, forever hang karega, ya throw karega?

std::promise<int> prom;
auto fut = prom.get_future();
prom.set_value(100);        // ready flag yahan true flip hota hai
int x = fut.get();          // ready already true hone KE BAAD call hota hai
  1. set_value(100) pehle run hota hai. Yeh step kyun? Yeh ready = true set karta hai aur cv par notify_all() call karta hai. Koi wait nahi kar raha abhi, isliye notify kisi ko nahi jagaata — aur yeh theek hai.
  2. fut.get() baad mein run hota hai. Yeh step kyun? Parent ke predicate cv.wait(lk, []{ return ready; }) ko yaad karo — jahan cv condition variable hai aur lk woh lock hai jo thread sote waqt hold karta hai (dono upar overview mein naam diye gaye hain). Kyunki ready pehle se true hai, predicate sone se pehle satisfy ho jaata hai, isliye wait bina cv ko kabhi touch kiye immediately return kar deta hai. Ek missed notify harmless hai — predicate, signal nahi, source of truth hai.

Verify: x == 100, zero blocking ke saath return hua. Yehi reason hai standard library predicate wait use karti hai, bare wait(lk) nahi.


Example 3 — Cell C: threads ke across ek exception transport karna

Forecast: kya program crash karega, ya main message catch kar lega?

void worker(std::promise<int> p) {
    try {
        throw std::runtime_error("disk on fire");
    } catch (...) {
        p.set_exception(std::current_exception());   // Step 1
    }
}
 
int main() {
    std::promise<int> prom;
    auto fut = prom.get_future();
    std::thread t(worker, std::move(prom));
    try {
        int x = fut.get();                            // Step 2
        std::cout << x;
    } catch (const std::exception& e) {
        std::cout << "caught: " << e.what() << "\n";  // Step 3
    }
    t.join();
}
  1. set_exception(std::current_exception()). Yeh step kyun? std::current_exception() currently in-flight exception ko grab karke isse exception_ptr ke roop mein package karta hai. Hum ise value ki jagah shared state ke exception slot mein store karte hain. (Dekho Exception handling in C++.)
  2. fut.get(). Yeh step kyun? Jab shared state mein ek exception ho, get() return nahi karta — woh rethrow_exception call karta hai, wahi same exception object yahan main ke thread mein re-raise karta hai.
  3. catch. Yeh step kyun? Kyunki exception get() call par re-throw hui thi, iske around ek ordinary try/catch kaam karta hai. Thread boundary ghum gayi.

Verify: output exactly caught: disk on fire hai, aur program terminate nahi karta. Sanity check: sirf exception slot use hua — set_value kabhi call nahi hua, isliye koi half-written value nahi hai.


Example 4 — Cell D: the broken promise

Forecast: kya get() forever hang karega (deadlock), ya ek specific error throw karega?

void worker(std::promise<int> p) {
    // oops: koi set_value nahi, koi set_exception nahi — p yahan destroy ho jaata hai
}
 
int main() {
    std::promise<int> prom;
    auto fut = prom.get_future();
    std::thread t(worker, std::move(prom));
    t.join();
    try {
        int x = fut.get();                                   // Step 2
        std::cout << x;
    } catch (const std::future_error& e) {
        std::cout << e.code().value() << "\n";               // Step 3
    }
}
  1. Promise unset destroy hoti hai. Yeh kyun matter karta hai? Standard kehta hai: agar promise ka shared state abandon ho jaaye (destructor bina value aur bina exception ke run kare), to destructor automatically future_error with code broken_promise store karta hai aur state ko ready mark karta hai. Yeh ek safety net hai taaki reader kabhi deadlock na kare.
  2. fut.get(). Yeh step kyun? State ready hai (auto-stored error ki wajah se), isliye get() unblock ho jaata hai — lekin woh ek exception hold karta hai, isliye future_error re-throw karta hai.
  3. Catch karke e.code() inspect karo. Yeh step kyun? Confirm karta hai ki error class broken_promise hai, kuch aur nahi.

Verify: get() throw karta hai, woh hang nahi karta. Library ki guarantee: ek unset promise phir bhi apne future ko jagaata hai, broken_promise ke saath. Isliye tumhare producer mein har path ya to set_value ya set_exception zaroor kare.


Example 5 — Cell E: one-shot violations (do get()s, do get_future()s)

Forecast: har abuse ke liye, kaun sa call throw karta hai aur fut.valid() use se just pehle kya read karta hai?

// Abuse (a): do get_future() calls
std::promise<std::string> prom;
auto f1 = prom.get_future();
// auto f2 = prom.get_future();   // THROWS: future_already_retrieved
 
// Abuse (b): do get() calls
prom.set_value("hello");
std::cout << f1.valid() << "\n";  // Step 2 -> 1
std::string s = f1.get();         // Step 3 : string ko move out karta hai
std::cout << f1.valid() << "\n";  // Step 4 -> 0
// f1.get();                      // Step 5 THROWS: no_state
  1. Doosra get_future() (abuse a). Illegal kyun? Shared state ek reading end ko diya ja sakta hai. Doosra retrieval do futures ko ek value ke liye ladhate hua create kar deta, isliye future_already_retrieved throw karta hai.
  2. Pehle get() se pehle valid(). Yeh step kyun? valid() poochta hai "kya main abhi bhi ek shared state se attached hoon?" Yahan woh 1 (true) hai — attached aur unread.
  3. Pehla get(). Yeh step kyun? Woh string ko shared state se move karke future ko detach kar deta hai.
  4. get() ke baad valid(). Yeh step kyun? Ab 0 (false) — future consume ho chuka hai aur koi state hold nahi karta.
  5. Doosra get() (abuse b). Illegal kyun? Koi state bacha nahi, isliye return karne ke liye kuch nahi, future_error(no_state) throw karta hai. Agar tumhe kai reads chahiye, to shared_future mein convert karo (Example 8).

Verify: valid() 1 phir 0 print karta hai, single legal get() ko bracket karta hai. Yeh woh cheap flag hai jo batata hai ki koi get() abhi allowed hai ya nahi.


Example 6 — Cell F: zero / degenerate payload, promise<void>

Forecast: set_value() bina T ke kaisa lagta hai, aur get() kya return karta hai?

#include <future>
#include <thread>
#include <iostream>
 
int main() {
    std::promise<void> done;                 // T is void: koi storage slot nahi
    std::future<void>  fut = done.get_future();
 
    std::thread t([p = std::move(done)]() mutable {
        // ... setup work karo ...
        p.set_value();                       // Step 2 : koi argument nahi!
    });
 
    fut.get();                               // Step 3 : void return karta hai, bas unblock karta hai
    std::cout << "proceeding\n";
    t.join();
}
  1. promise<void>. Yeh step kyun? Jab koi result nahi hota, value slot kuch nahi ban jaata. ready flag, mutex m aur condition variable cv abhi bhi wahan hain — signalling machinery hi chahiye thi, data nahi.
  2. Empty parentheses ke saath p.set_value(). Yeh step kyun? void mein store karne ke liye koi object nahi, isliye set_value pure "flip ready and notify" operation hai.
  3. void return karta hua fut.get(). Yeh step kyun? Woh abhi bhi ready hone tak block karta hai, phir kuch return nahi karta. Yeh do threads ke beech ek clean one-shot barrier hai — condition_variable jaisi hi guarantees lekin plumbing hidden.

Verify: conceptually, promise<void> = value cell remove, ready/mutex/cv retain. "proceeding" sirf worker ke set_value() ke baad print hota hai, jo prove karta hai ki empty-payload signal abhi bhi synchronize karta hai.


Example 7 — Cell H: limiting case, wait_for ke saath ek deadline

Forecast: 100 ms wait karne ke baad, wait_for kya kehta hai — ready, timeout, ya deferred?

using namespace std::chrono;
 
std::promise<int> prom;
auto fut = prom.get_future();
std::thread t([p = std::move(prom)]() mutable {
    std::this_thread::sleep_for(milliseconds(300));   // slow producer
    p.set_value(7);
});
 
auto status = fut.wait_for(milliseconds(100));        // Step 1
if (status == std::future_status::timeout) {
    std::cout << "gave up waiting\n";                 // Step 2
} else if (status == std::future_status::ready) {
    std::cout << fut.get() << "\n";
}
t.join();
  1. wait_for(100ms). Yeh step kyun? wait() (unconditionally block) ya get() (block aur consume) ke unlike, wait_for at most given duration ke liye block karta hai aur future_status return karta hai. Yeh value kabhi consume nahi karta, isliye ready hone par baad mein get() abhi bhi allowed hai.
  2. future_status::timeout par branch karo. Yeh step kyun? 100 ms baad producer (300 ms) done nahi hai, isliye status timeout hai aur hum give-up branch lete hain — koi crash nahi, koi hang nahi.

Verify: producer 300 ms aur deadline 100 ms ke saath, , isliye wait_for timeout return karta hai. (ready branch aur get()==7 tabhi fire karte jab deadline 300 ms se zyada hoti.)


Example 8 — Cell G: shared_future ke saath kai readers

Forecast: kaun sa ek conversion ek single-use future ko broadcastable banaata hai, aur kya har thread get() call kar sakta hai?

std::promise<int> prom;
std::shared_future<int> shared = prom.get_future().share();   // Step 1
 
auto reader = [shared](int id) {
    int v = shared.get();                                     // Step 3 : har ek theek se read karta hai
    std::cout << "reader " << id << " saw " << v << "\n";
};
 
std::thread a(reader, 1), b(reader, 2), c(reader, 3);
prom.set_value(55);                                           // Step 2
a.join(); b.join(); c.join();
  1. .share(). Yeh step kyun? future::share() shared state ko ek std::shared_future mein move karta hai, jo copyable hai aur jiska get() one-shot nahi hai — yeh har call par ek const&/copy return karta hai.
  2. set_value(55). Yeh step kyun? Ek producer abhi bhi exactly ek baar fulfill karta hai; broadcast reading side par hai.
  3. Teen get() calls. Yeh step kyun legal hai? shared_future ki teen copies mein se har ek get() call kar sakti hai; woh sab ek hi 55 observe karti hain. Ek plain future ke saath, sirf pehla succeed karta aur baaki no_state throw karte.

Verify: teeno readers 55 print karte hain; kul teen successful get()s (plain one-shot future ke saath impossible).


Example 9 — Cells I + J: real word problem, aur packaged_task twist

Forecast: main konsa final number print karta hai, aur kitne futures chahiye?

auto compute = [](std::promise<int> p, int a, int b) {
    p.set_value(a * b);
};
 
std::promise<int> pr, pp;
auto f_risk = pr.get_future();
auto f_pnl  = pp.get_future();
std::thread t1(compute, std::move(pr), 3, 100);   // risk = 300
std::thread t2(compute, std::move(pp), 5,  40);   // pnl  = 200
 
int total = f_risk.get() + f_pnl.get();           // Step 3
std::cout << total << "\n";
t1.join(); t2.join();
  1. Do promises, do futures. Kyun? Do independent results ⇒ do shared states concurrently chal rahe hain — yeh Cell I ka parallel-fan-out hai.
  2. Do threads simultaneously compute karte hain. Kyun? risk aur pnl ke beech koi data dependency nahi, isliye woh overlap karte hain. Dekho std::thread ki har ek kaise launch hoti hai.
  3. f_risk.get() + f_pnl.get(). Yeh step kyun? main har ek par baari baari block karta hai; total time ≈ dono mein se slower ka waqt, unka sum nahi.

Verify: .

Ab exam twist (Cell J) — jab tum std::packaged_task use karte ho to promise kahan hai?

std::packaged_task<int()> task([]{ return 6 * 7; });   // task andar ek promise chhupaata hai
std::future<int> fut = task.get_future();              // task se hi future
std::thread(std::move(task)).detach();                 // task run karna = ise fulfill karna
std::cout << fut.get() << "\n";                         // -> 42
  • Explicit promise kyun nahi? Ek packaged_task ek internal promise own karta hai. Task ko invoke karna automatically return value ke saath set_value call karta hai (ya throw karne par set_exception). Tum sirf future ke saath interact karte ho. std::async se compare karo, jo tumhare liye poora promise/thread/future trio banata hai.

Verify: packaged-task future yield karta hai, Example 1 jaisa bilkul, lekin promise machinery task ke andar hidden hai.


Recall

Recall Kaun sa cell block karne ki jagah throw karta hai, aur kyun?

Cell D (broken promise) ::: Promise ka destructor auto-store karta hai future_error(broken_promise) aur ready mark karta hai, isliye get() deadlock karne ki jagay throw karta hai.

Recall Example 2 ka

get() notify miss kyun nahi hua? Value-set-first ::: cv.wait(lk, []{return ready;}) sone se pehle predicate check karta hai; kyunki ready pehle se true tha to woh instantly return kar gaya — missed notify irrelevant hai.

Recall Ek one-shot future ko broadcast future mein kaun convert karta hai?

.share() ::: Yeh state ko ek copyable std::shared_future mein move karta hai jiska get() kai baar call ho sakta hai, har reader ke liye ek baar.

Recall Kaun sa call status deta hai value consume kiye bina?

wait_for ::: Given duration ke baad at most future_status::ready / timeout / deferred return karta hai, future ko invalidate kiye bina.