簡體   English   中英

std::future 可以比 std::promise 長壽嗎?

[英]Is it okay for std::future to outlive a std::promise?

clang ThreadSanitizer在以下代碼中報告數據競爭:

#include <future>
#include <iostream>
#include <vector>

int main() {
    std::cout << "start!" << std::endl;
    for (size_t i = 0; i < 100000; i++) {
        std::promise<void> p;
        std::future<void> f = p.get_future();

        std::thread t = std::thread([p = std::move(p)]() mutable {
            p.set_value();
        });

        f.get();
        t.join();
    }
    std::cout << "done!" << std::endl;
    return 0;
}

我可以通過將p = std::move(p)替換為&p來修復比賽。 但是,我找不到解釋promisefuture對象是否是線程安全的或者它們被銷毀的順序是否重要的文檔。 我的理解是,由於 promise 和未來通過“共享狀態”進行通信,因此 state 應該是線程安全的,銷毀順序應該無關緊要,但 TSan 不同意。 (沒有 TSan,程序似乎運行正常,而不是崩潰。)

這段代碼是否真的有潛在的競爭,或者這是一個 TSan 誤報?


您可以通過在 Ubuntu 19.10 Docker 容器中運行以下命令,使用 Clang 9 重現此問題:

$ docker run -it ubuntu:eoan /bin/bash

Inside container:

# apt update
# apt install clang-9 libc++-9-dev libc++abi-9-dev

# clang++-9 -fsanitize=thread -lpthread -std=c++17 -stdlib=libc++ -O0 -g test.cpp -o test
(See test.cpp file contents above)

# ./test

示例 output 顯示數據競爭(實際 output 在運行之間略有不同):

==================
WARNING: ThreadSanitizer: data race (pid=9731)
  Write of size 8 at 0x7b2000000018 by thread T14:
    #0 operator delete(void*) <null> (test+0x4b4e9e)
    #1 std::__1::__shared_count::__release_shared() <null> (libc++.so.1+0x83f2c)
    #2 std::__1::__tuple_leaf<1ul, test()::$_0, false>::~__tuple_leaf() /usr/lib/llvm-9/bin/../include/c++/v1/tuple:170:7 (test+0x4b7d38)
    #3 std::__1::__tuple_impl<std::__1::__tuple_indices<0ul, 1ul>, std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0>::~__tuple_impl() /usr/lib/llvm-9/bin/../include/c++/v1/tuple:361:37 (test+0x4b7ce9)
    #4 std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0>::~tuple() /usr/lib/llvm-9/bin/../include/c++/v1/tuple:466:28 (test+0x4b7c98)
    #5 std::__1::default_delete<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0> >::operator()(std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0>*) const /usr/lib/llvm-9/bin/../include/c++/v1/memory:2338:5 (test+0x4b7c16)
    #6 std::__1::unique_ptr<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0>, std::__1::default_delete<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0> > >::reset(std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0>*) /usr/lib/llvm-9/bin/../include/c++/v1/memory:2593:7 (test+0x4b7b80)
    #7 std::__1::unique_ptr<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0>, std::__1::default_delete<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0> > >::~unique_ptr() /usr/lib/llvm-9/bin/../include/c++/v1/memory:2547:19 (test+0x4b74ec)
    #8 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test()::$_0> >(void*) /usr/lib/llvm-9/bin/../include/c++/v1/thread:289:1 (test+0x4b7397)

  Previous atomic read of size 1 at 0x7b2000000018 by main thread:
    #0 pthread_cond_wait <null> (test+0x4268d8)
    #1 std::__1::condition_variable::wait(std::__1::unique_lock<std::__1::mutex>&) <null> (libc++.so.1+0x422de)
    #2 main /test/test.cpp:61:9 (test+0x4b713c)

  Thread T14 (tid=18144, running) created by main thread at:
    #0 pthread_create <null> (test+0x425c6b)
    #1 std::__1::__libcpp_thread_create(unsigned long*, void* (*)(void*), void*) /usr/lib/llvm-9/bin/../include/c++/v1/__threading_support:336:10 (test+0x4b958c)
    #2 std::__1::thread::thread<test()::$_0, void>(test()::$_0&&) /usr/lib/llvm-9/bin/../include/c++/v1/thread:303:16 (test+0x4b6fc4)
    #3 test() /test/test.cpp:44:25 (test+0x4b6d96)
    #4 main /test/test.cpp:61:9 (test+0x4b713c)

SUMMARY: ThreadSanitizer: data race (/test/test+0x4b4e9e) in operator delete(void*)
==================

promise超出 scope *時,會發生以下情況:

  • 如果共享 state 未准備好,
    • 在共享 state 中存儲一個future_error類型的異常,錯誤類型為broken_promise ,然后
    • 使 state 准備就緒
  • 否則,state 已准備就緒

如果在 promise 退出 scope 之前沒有設置任何值,那么在未來調用get()只會導致異常。

現在,實際上很難在共享 Z9ED39E2EA931586B3EZ58 之前從 scope 中制作 promise go 值。 無論如何,線程已經通過異常退出,或者您有一個邏輯錯誤,並非所有分支都調用promise::set_value

您的特定代碼似乎沒有出現任何這樣的症狀。 移動 promise 只需將共享 state 的所有權移動到新的 promise。

至於競爭條件, get_future保證不會與promise::set_value及其變體發生任何數據競爭。 future::get也保證等到 shared_state 准備好。 When a promise goes out of scope, it "releases" its shared state after making it ready, which would destroy the shared state only if it held the last reference to it. 由於您對它有另一個引用(通過未來),所以您是安全的。

現在,實現總是有可能存在數據競爭(偶然),但根據標准,您發布的代碼不應該有任何競爭。


*參考 [futures.state]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM