`std::future`--非同步的優勢

牛马chen發表於2024-10-07

std::future 相比於直接使用執行緒在 C++ 中有幾個重要的優勢,主要體現在同步結果獲取簡化程式碼管理、以及更安全的非同步任務管理等方面。以下是 std::future 的一些主要優勢:

1. 自動結果獲取與同步

  • std::future 提供了一種便捷的機制來獲取非同步任務的返回值。當我們使用執行緒時,通常無法輕鬆獲得執行緒的返回值,執行緒函式如果有返回值,需要透過共享變數、全域性變數或其他同步機制進行通訊,這使得程式碼更加複雜。

  • 透過 std::future,可以輕鬆獲取非同步任務的結果,它在內部同步管理任務執行的結果。

    例子:

    #include <iostream>
    #include <future>
    
    int compute() {
        return 42;
    }
    
    int main() {
        std::future<int> result = std::async(std::launch::async, compute);
        std::cout << "Result: " << result.get() << std::endl; // 自動等待執行緒完成並返回結果
        return 0;
    }
    

    在這種情況下,result.get() 會等待執行緒完成,並獲取非同步任務的返回值 42。這比直接使用執行緒共享狀態的方式要簡單得多。

2. 簡化程式碼管理

  • 使用 std::thread 需要手動管理執行緒的生命週期,比如使用 join() 來等待執行緒執行完成。而 std::future 透過 get() 或者 wait() 來等待任務完成,自動管理同步,不需要手動呼叫 join()

    直接使用執行緒時:

    #include <iostream>
    #include <thread>
    
    void compute(int &result) {
        result = 42;
    }
    
    int main() {
        int result;
        std::thread t(compute, std::ref(result));
        t.join(); // 必須手動呼叫 join() 等待執行緒完成
        std::cout << "Result: " << result << std::endl;
        return 0;
    }
    

    這裡要透過 std::ref(result) 共享資料,並且必須手動管理執行緒的結束 (join()),否則程式會發生錯誤。

3. 更安全的非同步任務管理

  • std::futurestd::async 可以更好地管理非同步任務,避免直接操作執行緒帶來的錯誤。比如,當執行緒沒有被正確 join 時,程式可能崩潰,而 std::future 會自動等待非同步任務完成。

  • 如果程式異常退出或者忘記呼叫 join()std::thread 會導致程式中斷或者未定義行為。而 std::future 不會發生這些問題,它透過 get() 自動等待非同步任務完成。

    例如,如果使用 std::thread,忘記 join(),程式會出現崩潰風險:

    std::thread t([] { std::this_thread::sleep_for(std::chrono::seconds(1)); });
    // 如果沒有 t.join(),程式將崩潰
    

4. 異常管理

  • std::future 可以捕獲非同步任務中的異常,而直接使用執行緒時,異常管理需要額外的工作。在 std::future 中,get() 不僅可以獲取任務的結果,還可以在任務中出現異常時,將該異常丟擲,方便後續處理。

    例子:

    #include <iostream>
    #include <future>
    #include <stdexcept>
    
    int faulty_task() {
        throw std::runtime_error("Something went wrong!");
    }
    
    int main() {
        std::future<int> result = std::async(std::launch::async, faulty_task);
        try {
            int value = result.get(); // 在此捕獲異常
        } catch (const std::exception& e) {
            std::cerr << "Exception: " << e.what() << std::endl;
        }
        return 0;
    }
    

    直接使用 std::thread 時,要處理任務中的異常需要複雜的機制,std::future 簡化了這一過程。

5. 延遲啟動任務

  • 使用 std::asyncstd::future,你可以選擇是否立即啟動執行緒,或者延遲執行任務(惰性啟動)。透過 std::async 的第二個引數(如 std::launch::deferred),可以控制任務是否非同步執行或延遲執行,這種靈活性在直接使用執行緒時無法輕易實現。

    惰性啟動:

    std::future<int> result = std::async(std::launch::deferred, compute); // 任務並不會立即執行
    // result.get() 執行時,任務才開始執行
    

總結

std::future 的主要優勢在於:

  • 簡化了非同步任務的結果獲取與同步操作;
  • 提供了更好的異常管理;
  • 避免了手動管理執行緒生命週期的複雜性;
  • 提供了延遲執行(deferred execution)的靈活性。

相比之下,std::thread 直接操作執行緒,雖然可以讓程式設計師顯式控制任務,但在實際開發中,這種顯式控制常常導致複雜的程式碼管理和更大的錯誤風險,因此 std::future 是一種更高層次、更安全的選擇。

相關文章