C++ Promise: Giao Kèo Tương Lai Trong Lập Trình Bất Đồng Bộ
C++

C++ Promise: Giao Kèo Tương Lai Trong Lập Trình Bất Đồng Bộ

Author

Admin System

@root

Ngày xuất bản

25 Mar, 2026

Lượt xem

6 Lượt

"promise"

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ột int.
  • std::future<int> f = p.get_future();: Lấy "chìa khóa" để mở lời hứa đó. future này sẽ "đợi" cho đến khi promise được set_value hoặc set_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óa f để lấy kết quả. Nếu promise chư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 đó.
Illustration

Mẹo (Best Practices) để không bị "toang" khi dùng std::promise

  1. Luôn đi đôi với std::future: Đã có promise thì phải có future đi kèm. Một promise chỉ có thể có một future duy nhất. get_future() chỉ gọi được 1 lần.
  2. set_value hoặc set_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ọi set_value hoặc set_exception lần thứ hai sẽ gây ra lỗi std::future_error.
  3. Xử lý exception cẩn thận: Luôn bọc future.get() trong try-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.
  4. std::move the promise: Khi truyền std::promise vào một thread mới, hãy dùng std::move để chuyển quyền sở hữu. std::promise không thể copy được.
  5. join hoặc detach thread: Đừ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ặc detach() 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 promise tớ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_value khi hoàn thành phần việc của mình, và thread chính sẽ get cá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ới std::promise bạn tự tay tạo std::thread và truyền promise và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::promise cho 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_for trong hàm complexCalculation để xem future.get() block như thế nào.
  • Thử không gọi set_value hay set_exception để xem điều gì xảy ra khi future.get() bị gọi (nó sẽ block mãi mãi, hoặc cho đến khi promise bị destroy).
  • Thử gọi set_value hai lần để thấy lỗi std::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é!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!