
Chào các dân chơi công nghệ, anh Creyt đây! Hôm nay chúng ta sẽ cùng "flex" kiến thức về một khái niệm nghe thì có vẻ hơi "deep web" nhưng thực ra lại cực kỳ "chill" và hữu ích trong C++: std::promise. Nghe cái tên promise (lời hứa) là thấy mùi "giao kèo" rồi đúng không? Chính xác luôn!
std::promise là gì mà nghe ngầu vậy?
Đơn giản mà nói, std::promise trong C++ giống như một hộp thư bí mật một chiều hoặc một giao kèo tương lai giữa hai người bạn (mà ở đây là hai thread - luồng thực thi). Một thread (người gửi) sẽ "hứa" là sẽ gửi một cái gì đó (một giá trị, một kết quả, hoặc thậm chí là một cái "phốt" - lỗi) vào hộp thư này. Và một thread khác (người nhận) sẽ "đợi" ở đầu bên kia của hộp thư đó, cầm trên tay một cái chìa khóa tên là std::future, để chờ cái "lời hứa" kia được thực hiện.
Mục đích chính của nó là để truyền kết quả hoặc exception từ một luồng thực thi này sang một luồng thực thi khác một cách an toàn và bất đồng bộ. Tức là, thay vì phải đợi nhau làm xong việc rồi mới làm tiếp (kiểu "blocking"), các thread có thể chạy song song và chỉ "gặp nhau" khi cần trao đổi kết quả thôi. "No-block, just chill!"
Code Ví Dụ Minh Họa: Giao kèo "Làm Toán" và "Báo Cáo"
Để dễ hình dung, hãy tưởng tượng bạn có một "thằng em" (một thread) chuyên đi làm mấy phép tính "hack não" và bạn (thread chính) thì cần kết quả để "báo cáo sếp".
#include <iostream>
#include <thread> // Để tạo thread
#include <future> // Để dùng std::promise và std::future
#include <chrono> // Để giả lập thời gian làm việc
#include <stdexcept> // Để ném exception
// Hàm giả lập "thằng em" làm việc
void complexCalculation(std::promise<int> p, int a, int b) {
try {
std::cout << "Thằng em: Đang vắt óc tính toán...\n";
std::this_thread::sleep_for(std::chrono::seconds(2)); // Giả lập làm việc 2 giây
if (b == 0) {
throw std::runtime_error("Thằng em: Rớt môn Chia cho 0 rồi sếp ơi!");
}
int result = a / b;
std::cout << "Thằng em: Xong rồi sếp! Kết quả là " << result << "\n";
p.set_value(result); // "Thực hiện lời hứa": Gửi kết quả đi
} catch (const std::exception& e) {
std::cout << "Thằng em: Có biến! " << e.what() << "\n";
p.set_exception(std::current_exception()); // "Thực hiện lời hứa": Gửi lỗi đi
}
}
int main() {
// Case 1: Thành công
std::cout << "--- CASE 1: THÀNH CÔNG ---\n";
std::promise<int> promise1; // Tạo lời hứa
std::future<int> future1 = promise1.get_future(); // Lấy chìa khóa (future) để chờ kết quả
// Khởi tạo "thằng em" (thread) và giao việc
std::thread worker1(complexCalculation, std::move(promise1), 10, 2);
std::cout << "Anh: Đang chờ thằng em báo cáo...\n";
try {
int finalResult = future1.get(); // "Anh" chờ và lấy kết quả từ "chìa khóa"
std::cout << "Anh: Tuyệt vời! Kết quả cuối cùng là: " << finalResult << "\n";
} catch (const std::exception& e) {
std::cout << "Anh: Toang rồi! Nhận được lỗi: " << e.what() << "\n";
}
worker1.join(); // Đợi "thằng em" làm xong việc (quan trọng để tránh crash)
std::cout << "\n";
// Case 2: Xử lý lỗi
std::cout << "--- CASE 2: XỬ LÝ LỖI ---\n";
std::promise<int> promise2;
std::future<int> future2 = promise2.get_future();
std::thread worker2(complexCalculation, std::move(promise2), 10, 0); // Thử chia cho 0
std::cout << "Anh: Đang chờ thằng em báo cáo...\n";
try {
int finalResult = future2.get();
std::cout << "Anh: Tuyệt vời! Kết quả cuối cùng là: " << finalResult << "\n";
} catch (const std::exception& e) {
std::cout << "Anh: Toang rồi! Nhận được lỗi: " << e.what() << "\n";
}
worker2.join();
return 0;
}
Trong ví dụ trên:
std::promise<int> p;: Tạo một lời hứa sẽ trả về mộtint.std::future<int> f = p.get_future();: Lấy "chìa khóa" để mở lời hứa đó.futurenày sẽ "đợi" cho đến khipromiseđượcset_valuehoặcset_exception.p.set_value(result);: Khi "thằng em" tính xong, nó "thực hiện lời hứa" bằng cách gửi kết quả.p.set_exception(std::current_exception());: Nếu có lỗi, nó gửi cái lỗi đó đi.f.get();: "Anh" sẽ dùng chìa khóafđể lấy kết quả. Nếupromisechưa được set,get()sẽ chặn (block) cho đến khi có kết quả hoặc lỗi. Nếu là lỗi, nó sẽ re-throw cái exception đó.

Mẹo (Best Practices) để không bị "toang" khi dùng std::promise
- Luôn đi đôi với
std::future: Đã cópromisethì phải cófutuređi kèm. Mộtpromisechỉ có thể có mộtfutureduy nhất.get_future()chỉ gọi được 1 lần. set_valuehoặcset_exceptionĐÚNG MỘT LẦN: Giống như "lời hứa" vậy, bạn chỉ hứa và thực hiện nó một lần thôi. Gọiset_valuehoặcset_exceptionlần thứ hai sẽ gây ra lỗistd::future_error.- Xử lý exception cẩn thận: Luôn bọc
future.get()trongtry-catchđể bắt các lỗi mà luồng worker có thể gửi về. Đây là một cách cực kỳ hiệu quả để truyền lỗi giữa các thread. std::movethepromise: Khi truyềnstd::promisevào một thread mới, hãy dùngstd::moveđể chuyển quyền sở hữu.std::promisekhông thể copy được.joinhoặcdetachthread: Đừng quên xử lý thread sau khi nó hoàn thành.join()để đảm bảo thread kết thúc trước khi chương trình chính thoát, hoặcdetach()nếu bạn muốn thread chạy độc lập và không cần chờ nó.
Góc học thuật "Harvard" (nhưng vẫn dễ hiểu)
std::promise là một viên gạch quan trọng trong kiến trúc lập trình bất đồng bộ của C++. Nó cung cấp một kênh giao tiếp một lần (one-shot communication channel) giữa producer (thread tạo ra kết quả) và consumer (thread chờ kết quả). Khác với std::async (thường đơn giản hơn, C++ runtime tự quản lý thread và future), std::promise cho bạn quyền kiểm soát cao hơn về việc khi nào và làm thế nào kết quả được "set".
Nó là một phần của hệ thống std::future - một cơ chế mạnh mẽ để đồng bộ hóa các tác vụ chạy song song mà không cần dùng đến các cơ chế khóa phức tạp như mutex hay condition_variable cho việc trao đổi kết quả. std::promise tách biệt việc tính toán và việc cung cấp kết quả, giúp code của bạn sạch sẽ, dễ đọc và dễ bảo trì hơn trong môi trường đa luồng.
Ứng dụng thực tế: "Promise" ở khắp mọi nơi!
- Web Servers: Khi một web server nhận hàng ngàn request cùng lúc, mỗi request có thể được xử lý trong một thread riêng. Kết quả của việc xử lý (ví dụ: dữ liệu từ database) sẽ được gửi về qua một
promisetới thread chính để tạo response gửi lại cho client. Tối ưu hóa hiệu suất, tránh tắc nghẽn. - Game Development: Imagine bạn đang chơi game, và game cần load một đống texture hoặc tính toán AI của đối thủ. Thay vì làm game bị "đứng hình" (blocking), những tác vụ nặng này sẽ được đẩy sang các thread nền và "hứa" sẽ trả về kết quả qua
promise. Game vẫn mượt mà, "như chưa hề có cuộc chia ly". - Data Processing Pipelines: Khi xử lý lượng lớn dữ liệu, bạn có thể chia nhỏ dữ liệu thành nhiều phần và giao cho các thread khác nhau xử lý song song. Mỗi thread sẽ
set_valuekhi hoàn thành phần việc của mình, và thread chính sẽgetcác kết quả đó để tổng hợp.
Thử nghiệm và Nên dùng cho case nào?
Anh Creyt đã từng "đau đầu" với việc truyền kết quả giữa các thread mà không bị "deadlock" hay "race condition". std::promise chính là "liều thuốc" thần kỳ cho những case đó.
Khi nào nên dùng std::promise?
- Bạn muốn kiểm soát việc tạo và quản lý thread: Khác với
std::async(nơi C++ runtime quyết định có tạo thread mới hay không), vớistd::promisebạn tự tay tạostd::threadvà truyềnpromisevào. - Kết quả không đến từ một hàm đơn lẻ: Đôi khi, kết quả bạn chờ đợi không phải là return value của một hàm, mà là từ một chuỗi các sự kiện, một callback, hoặc một quá trình phức tạp hơn diễn ra trong một thread khác.
std::promisecho phép bạn "set" kết quả bất cứ lúc nào trong luồng hoạt động của thread đó. - Cần truyền exception giữa các thread: Đây là một trong những điểm mạnh nhất. Khi một thread gặp lỗi, nó có thể báo cáo lỗi đó về thread chính thông qua
set_exception, giúp bạn tập trung xử lý lỗi ở một chỗ.
Thử nghiệm thêm:
- Thử thay đổi thời gian
sleep_fortrong hàmcomplexCalculationđể xemfuture.get()block như thế nào. - Thử không gọi
set_valuehayset_exceptionđể xem điều gì xảy ra khifuture.get()bị gọi (nó sẽ block mãi mãi, hoặc cho đến khipromisebị destroy). - Thử gọi
set_valuehai lần để thấy lỗistd::future_error.
std::promise không chỉ là một công cụ, nó là một "mindset" về cách bạn nghĩ về việc giao tiếp và đồng bộ hóa trong thế giới đa luồng. Nắm vững nó, bạn sẽ "level up" kỹ năng C++ của mình lên một tầm cao mới!
Thuộc Series: C++
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!