std::thread 构造函数完成实际上与执行线程的开始同步吗?

Does std::thread constructor completion actually synchronize with the beginning of the thread of execution?

C++11 标准 (N337, 30.3.1.2) 说明了 std::thread 构造函数的同步:

Synchronization: The completion of the invocation of the constructor synchronizes with the beginning of the invocation of the copy of f.

阅读它,我认为构造函数在新线程启动之前完成。但是根据问题 (std::thread construction and execution) 和 libc++/libstdc++ 中的当前实现,似乎没有同步机制,新的执行线程可能会在 std::thread 构造函数结束之前开始。

如果那是正确的,那么该标准试图说明什么?这是标准和实施之间的差距吗?还是我对“同步”一词的理解不正确?即使构造函数和新线程同时运行,是否可以认为构造函数完成与新线程开始同步?

一旦构造函数完成,您就无法阻止线程执行,如果它已经启动或没有立即执行是无关紧要的,取决于 scheduler/cpu/load。

但是你可以保证在线程代码开始执行时,构造函数中的所有代码都已执行。

换句话说,可能是构造函数执行完后,线程在下一条指令之前执行了一百万条指令(比如主线程在创建新线程后立即挂起),也可能是构造函数在线程中的第一条指令执行之前执行(即新线程立即挂起)。

可能是硬件是单核的,实际上根本不可能“同时”执行两条指令,并且对本机类型的所有操作都是原子的(这是程序首先开始执行时的主要问题真正的并行硬件......许多旧的多线程代码在没有显式同步的情况下工作,因为硬件总是隐式同步但当真正的并行性到来时开始以随机方式失败)。

无论如何,我不会尝试过多地阅读正式规范:从纯粹的正式角度来看,我认为很难说从未真正启动线程的实现不符合要求(证明黑盒不符合要求需要等到时间结束)。对于 a+b 需要 100 亿年的实现也可以这样说...

不,它不同步(调度等...)

我通常使用条件变量构造来真正确定异步线程是否真正启动。像这样:

#include <condition_variable>
#include <mutex>
#include <thread>

/// <summary>
/// wrapper for a condition variable, takes into account
/// https://www.modernescpp.com/index.php/c-core-guidelines-be-aware-of-the-traps-of-condition-variables
/// </summary>
class sync_signal final
{
public:
    sync_signal() = delete;
    ~sync_signal() = default;
    sync_signal(const sync_signal&) = delete;
    sync_signal& operator=(const sync_signal&) = delete;
    sync_signal(sync_signal&&) = delete;

    explicit sync_signal(bool value) :
        m_value{ value }
    {
    }

    void set() noexcept
    {
        {
            std::unique_lock<std::mutex> lock(m_value_mutex);
            m_value = true;
        }
        m_value_changed.notify_all();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock(m_value_mutex);
        auto pred = [this] { return m_value; };
   
        // check pred first we might have missed a notify
        if (pred())
        {
            return;
        }

        m_value_changed.wait(lock, pred);
    }

    std::mutex m_value_mutex;
    std::condition_variable m_value_changed;
    bool m_value;
};


int main() 
{
    sync_signal signal{ false };

    std::thread t([&signal]()
    {
        signal.set(); // signal that thread has really scheduled/started
        // do your async stuff here
    });

    // wait on main thread for async thread to have really started
    signal.wait();

    // wait for thread to finish to avoid race conditions at shut-down
    t.join();
}

不过对于持续时间较短的函数,我更喜欢使用 std::async。 它不会创建一个全新的线程(并占用资源),而是从线程池中获取一个线程

#include <future>

auto ft = std::async(std::launch::async, [&signal]()
{
    signal.set(); // signal that thread has really started
    // do your async stuff here
});

ft.get();

Reading it, I thought the constructor completes before the start of the new thread

“同步”是 term of art. When the standard mandates that two operations synchronize with each other, that carries with it certain requirements for evaluations before and after the two operations. For example, accessing a variable in the original thread before the std::thread constructor, and accessing it in the new thread do not cause a data race

直觉上,您可以将“同步”视为意味着新线程可以从初始线程看到所有先前的评估、修改和副作用。

无需确保线程在构造函数结束时开始。这不是这个意思。

标准库强制执行此要求的方式是依赖像 pthreads 这样本质上也执行此要求的基础库。