Giao diện
🚀 async & future — Higher-level Abstractions
std::async và std::future cho phép chạy code bất đồng bộ mà không cần quản lý threads thủ công. Tại sao Higher Abstractions?
❌ Vấn đề với raw threads
cpp
#include <thread>
#include <iostream>
int compute() {
// Làm gì đó nặng...
return 42;
}
int main() {
int result;
std::thread t([&result]() {
result = compute(); // ⚠️ Race condition potential
});
t.join();
std::cout << result << std::endl;
return 0;
}Vấn đề:
- Phải quản lý thread lifecycle (
join,detach) - Return value phức tạp (cần shared variable)
- Exception handling khó khăn
✅ Giải pháp: std::async
cpp
#include <future>
#include <iostream>
int compute() {
return 42;
}
int main() {
// ✅ Simple and clean!
std::future<int> result = std::async(compute);
std::cout << result.get() << std::endl; // 42
return 0;
}Analogy: Đặt hàng nhà hàng
┌─────────────────────────────────────────────────────────────────┐
│ RESTAURANT ANALOGY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ YOU (Main thread) KITCHEN (Worker thread) │
│ ───────────────── ──────────────────────── │
│ │
│ 1. Order food std::async(cook_food) │
│ │ │
│ ▼ │
│ ┌───────────┐ ┌───────────────────┐ │
│ │ RECEIPT │ ◄─────────── │ PROMISE │ │
│ │ (future) │ │ (kitchen commits) │ │
│ └───────────┘ └───────────────────┘ │
│ │ │ │
│ │ Do other things... │ Cooking... │
│ │ │ │
│ ▼ ▼ │
│ result.get() ◄─────────── promise.set_value(food) │
│ (Wait & receive) (Food ready!) │
│ │
│ 🍜 Enjoy! │
│ │
└─────────────────────────────────────────────────────────────────┘- future = Receipt (phiếu nhận hàng) — bạn giữ nó và đợi
- promise = Kitchen's commitment — nhà bếp hứa sẽ làm xong
- get() = Lấy hàng — chờ nếu chưa xong
std::async — Fire and Forget
Basic Usage
cpp
#include <future>
#include <iostream>
#include <chrono>
int heavyComputation(int x) {
std::this_thread::sleep_for(std::chrono::seconds(2));
return x * x;
}
int main() {
std::cout << "Starting async task...\n";
// Launch async task
std::future<int> result = std::async(heavyComputation, 10);
// Do other work while computation runs...
std::cout << "Doing other work...\n";
// Get result (blocks if not ready)
int value = result.get();
std::cout << "Result: " << value << std::endl; // 100
return 0;
}Launch Policies
cpp
#include <future>
#include <iostream>
#include <thread>
void showThreadId() {
std::cout << "Task thread: " << std::this_thread::get_id() << std::endl;
}
int main() {
std::cout << "Main thread: " << std::this_thread::get_id() << std::endl;
// std::launch::async — FORCE chạy trên thread mới
auto f1 = std::async(std::launch::async, showThreadId);
f1.get(); // Thread ID khác main
// std::launch::deferred — Lazy evaluation, chạy khi gọi get()
auto f2 = std::async(std::launch::deferred, showThreadId);
f2.get(); // Thread ID GIỐNG main (chạy trên main thread)
// Default: async | deferred — Compiler quyết định
auto f3 = std::async(showThreadId);
f3.get(); // Có thể giống hoặc khác main
return 0;
}┌─────────────────────────────────────────────────────────────────┐
│ LAUNCH POLICIES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ std::launch::async │
│ ───────────────── │
│ • FORCE tạo thread mới │
│ • Task chạy NGAY LẬP TỨC │
│ • Dùng khi cần parallel execution │
│ │
│ std::launch::deferred │
│ ────────────────────── │
│ • KHÔNG tạo thread mới │
│ • Task chạy KHI GỌI get() hoặc wait() │
│ • Dùng khi muốn lazy evaluation │
│ │
│ Default (async | deferred) │
│ ────────────────────────── │
│ • Compiler/runtime quyết định │
│ • ⚠️ Có thể không chạy nếu không gọi get()! │
│ │
└─────────────────────────────────────────────────────────────────┘⚠️ Default Policy Trap
Với default policy, nếu bạn không gọi get() hoặc wait(), task có thể không bao giờ chạy!
std::future — Handle to Async Result
API Overview
cpp
std::future<T> f = std::async(...);
// Get result (blocks, can only call ONCE)
T value = f.get();
// Wait without getting result
f.wait();
// Wait with timeout
std::future_status status = f.wait_for(std::chrono::seconds(1));
if (status == std::future_status::ready) {
// Result is ready
} else if (status == std::future_status::timeout) {
// Still running
} else if (status == std::future_status::deferred) {
// Hasn't started (deferred policy)
}
// Check if future is valid
bool valid = f.valid();Timeout Pattern
cpp
#include <future>
#include <iostream>
#include <chrono>
int slowTask() {
std::this_thread::sleep_for(std::chrono::seconds(5));
return 42;
}
int main() {
auto future = std::async(std::launch::async, slowTask);
// Poll with timeout
while (true) {
auto status = future.wait_for(std::chrono::milliseconds(500));
if (status == std::future_status::ready) {
std::cout << "Result: " << future.get() << std::endl;
break;
}
std::cout << "Still waiting...\n";
}
return 0;
}Output:
Still waiting...
Still waiting...
Still waiting...
...
Result: 42Exception Propagation
Exceptions trong async task được propagate khi gọi get():
cpp
#include <future>
#include <iostream>
#include <stdexcept>
int riskyTask() {
throw std::runtime_error("Something went wrong!");
return 42;
}
int main() {
auto future = std::async(std::launch::async, riskyTask);
try {
int result = future.get(); // ❌ Exception thrown here!
} catch (const std::exception& e) {
std::cout << "Caught: " << e.what() << std::endl;
}
return 0;
}💡 KEY INSIGHT
Exception được "lưu trữ" trong future và chỉ được throw khi bạn gọi get(). Đây là cách clean để handle errors từ async tasks.
std::promise — Producer Side
std::promise là producer — nó "hứa" sẽ cung cấp một giá trị:
cpp
#include <future>
#include <thread>
#include <iostream>
void producer(std::promise<int>& prom) {
// Do some work...
std::this_thread::sleep_for(std::chrono::seconds(1));
// Fulfill the promise
prom.set_value(42);
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future(); // Get future from promise
std::thread t(producer, std::ref(prom));
std::cout << "Waiting for result...\n";
int result = fut.get(); // Block until promise is fulfilled
std::cout << "Got: " << result << std::endl;
t.join();
return 0;
}Setting Exceptions
cpp
void riskyProducer(std::promise<int>& prom) {
try {
// Something might fail...
throw std::runtime_error("Failed!");
} catch (...) {
// Pass exception to future
prom.set_exception(std::current_exception());
}
}Promise Use Cases
| Use Case | Description |
|---|---|
| Thread signaling | Notify another thread that work is done |
| One-shot events | Single producer, single consumer |
| Custom async patterns | When std::async doesn't fit |
std::shared_future — Multiple Consumers
std::future chỉ có thể get() một lần. Dùng std::shared_future cho nhiều consumers:
cpp
#include <future>
#include <thread>
#include <iostream>
#include <vector>
int compute() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}
void consumer(std::shared_future<int> fut, int id) {
int value = fut.get(); // ✅ Mỗi consumer đều có thể get()
std::cout << "Consumer " << id << " got: " << value << std::endl;
}
int main() {
std::shared_future<int> fut = std::async(std::launch::async, compute).share();
std::vector<std::thread> consumers;
for (int i = 0; i < 3; ++i) {
consumers.emplace_back(consumer, fut, i);
}
for (auto& t : consumers) {
t.join();
}
return 0;
}Output:
Consumer 0 got: 42
Consumer 1 got: 42
Consumer 2 got: 42Practical Examples
Parallel Computation
cpp
#include <future>
#include <vector>
#include <numeric>
#include <iostream>
long long parallelSum(const std::vector<int>& data) {
size_t mid = data.size() / 2;
// Compute first half async
auto futureFirstHalf = std::async(std::launch::async, [&]() {
return std::accumulate(data.begin(), data.begin() + mid, 0LL);
});
// Compute second half on this thread
long long secondHalf = std::accumulate(data.begin() + mid, data.end(), 0LL);
// Combine results
return futureFirstHalf.get() + secondHalf;
}
int main() {
std::vector<int> data(10000000, 1); // 10 million 1s
auto start = std::chrono::high_resolution_clock::now();
long long sum = parallelSum(data);
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Sum: " << sum << std::endl;
std::cout << "Time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< "ms\n";
return 0;
}Multiple Async Tasks
cpp
#include <future>
#include <iostream>
#include <string>
std::string fetchFromDB() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
return "DB Data";
}
std::string fetchFromAPI() {
std::this_thread::sleep_for(std::chrono::milliseconds(300));
return "API Data";
}
std::string fetchFromCache() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
return "Cache Data";
}
int main() {
// Fire all requests in parallel
auto dbFuture = std::async(std::launch::async, fetchFromDB);
auto apiFuture = std::async(std::launch::async, fetchFromAPI);
auto cacheFuture = std::async(std::launch::async, fetchFromCache);
// Wait for all (total time = max(200, 300, 50) = 300ms instead of 550ms)
std::cout << cacheFuture.get() << std::endl;
std::cout << dbFuture.get() << std::endl;
std::cout << apiFuture.get() << std::endl;
return 0;
}📚 Tổng kết
| Component | Role | Analogy |
|---|---|---|
std::async | Launch async task | Đặt hàng |
std::future | Handle to result | Phiếu nhận hàng |
std::promise | Producer side | Nhà bếp cam kết |
std::shared_future | Multiple consumers | Copy phiếu cho nhiều người |
| Launch Policy | Behavior |
|---|---|
std::launch::async | Force new thread |
std::launch::deferred | Lazy, run on get() |
| Default | Implementation-defined |
➡️ Tiếp theo
Async/future vẫn có overhead (thread creation, synchronization). Để performance cực cao, chúng ta cần lock-free programming...
Atomics → — std::atomic cho lock-free basics.
🧠 Quiz
Câu 1: std::launch::deferred có ý nghĩa gì khi dùng với std::async?
- [ ] A) Task chạy trên thread mới ngay lập tức
- [x] B) Task chỉ được thực thi khi gọi
get()hoặcwait()trên future - [ ] C) Task bị hủy nếu không ai dùng kết quả
- [ ] D) Task chạy với priority thấp hơn các task khác
💡 Giải thích:
std::launch::deferredbiến task thành lazy evaluation — function chỉ chạy khi bạn thực sự cần kết quả (gọiget()hoặcwait()). Task chạy trên thread gọiget(), không tạo thread mới. Hữu ích khi muốn trì hoãn computation tốn kém cho đến khi thật sự cần.
Câu 2: Khi gọi future.get() lần thứ hai trên cùng một std::future, điều gì xảy ra?
- [ ] A) Trả về kết quả đã cache từ lần đầu
- [x] B) Undefined behavior hoặc throw exception vì future không còn valid
- [ ] C) Block chờ task chạy lại từ đầu
- [ ] D) Trả về giá trị mặc định của kiểu dữ liệu
💡 Giải thích:
std::future::get()là move-only operation — sau khi gọi, future chuyển sang trạng thái invalid (không còn shared state). Gọiget()lần hai sẽ throwstd::future_error. Nếu cần nhiều consumer nhận cùng kết quả, dùngstd::shared_futurethay thế.
Câu 3: std::shared_future giải quyết vấn đề gì mà std::future không làm được?
- [ ] A) Tăng tốc độ tính toán bằng parallel execution
- [x] B) Cho phép nhiều threads cùng nhận kết quả từ một async task
- [ ] C) Tự động chia task thành nhiều subtasks
- [ ] D) Tự động retry khi task throw exception
💡 Giải thích:
std::futurechỉ cho phép một consumer gọiget()(move semantics).std::shared_futurecho phép nhiều threads cùng chờ và nhận kết quả từ một task —get()trả về const reference và có thể gọi nhiều lần. Tạo bằngfuture.share()hoặc từstd::promise::get_future().