简体   繁体   English

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

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

The C++11 standard (N337, 30.3.1.2) states about the synchronization of the std::thread constructor: 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.同步:构造函数调用的完成与 f 的副本调用的开始同步。

Reading it, I thought the constructor completes before the start of the new thread.阅读它,我认为构造函数在新线程开始之前完成。 But according to the question ( std::thread construction and execution ) and the current implementation in libc++/libstdc++, there seems no synchronization mechanism and the new thread of execution can possibly begin before the end of the std::thread constructor.但根据问题( std::thread 构造和执行)和 libc++/libstdc++ 中的当前实现,似乎没有同步机制,新的执行线程可能在 std::thread 构造函数结束之前开始。

If that is correct, what does the standard try to say?如果那是正确的,那么该标准试图说明什么? Is this the gap between standard and implementation?这是标准和实施之间的差距吗? Or do I understand the term "synchronize with" incorrectly?还是我对“同步”一词的理解不正确? Even if constructor and new thread are running simultaneously, can the constructor completion be considered synchronizing with the beginning of new thread?即使构造函数和新线程同时运行,是否可以认为构造函数完成与新线程开始同步?

Reading it, I thought the constructor completes before the start of the new thread阅读它,我认为构造函数在新线程开始之前完成

"synchronizes with" is a 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 .例如,在std::thread构造函数之前在原始线程中访问变量,并在新线程中访问它不会导致数据竞争

Intuitively, you can think of "synchronizes with" as meaning that the new thread can see all prior evaluations, modifications, and side effects from the initial thread.直观地,您可以将“同步”视为意味着新线程可以从初始线程看到所有先前的评估、修改和副作用。

There is no need to make sure the thread begins by the end of the constructor.无需确保线程在构造函数结束时开始。 That is not what this says.这不是这个意思。

The way standard libraries enforce this requirement is by relying on underlying libraries like pthreads that essentially also enforce this requirement.标准库执行此要求的方式是依赖底层库,如pthreads ,这些库本质上也执行此要求。

Once the constructor completes there is nothing you can do to prevent the thread from executing, if it already starts or not actual execution immediately is irrelevant and depends on scheduler/cpu/load.一旦构造函数完成,您就无法阻止线程执行,如果它已经启动或没有立即执行是无关紧要的,取决于调度程序/cpu/负载。

You are guaranteed however that all the code in the constructor has been executed when the code of the thread starts executing.但是,您可以保证在线程代码开始执行时,构造函数中的所有代码都已执行。

In other words it may be that the thread executes a million instructions before the next instruction after the constructor is executed (eg the main thread is suspended right after creating the new thread) or it may be a million instructions after the constructor are executed before the first instruction in the thread is executed (ie the new thread is immediately suspended).换句话说,可能是线程在执行构造函数后的下一条指令之前执行了一百万条指令(例如,主线程在创建新线程后立即挂起)或者可能是在构造函数执行之后执行了一百万条指令之后线程中的第一条指令被执行(即新线程立即挂起)。

May be the hardware is single core and indeed there is simply no way two instructions are executed "at the same time" and all operations on native types are atomic (this was the main issue when programs started being executed on first real parallel hardware... a lot of old multithread code was working without explicit synchronization because the hardware was always implicitly synchronizing but started failing in random ways when true parallelism arrived).可能是硬件是单核的,实际上根本不可能“同时”执行两条指令,并且对本机类型的所有操作都是原子的(这是程序开始在第一个真正的并行硬件上执行时的主要问题...... . 许多旧的多线程代码在没有显式同步的情况下工作,因为硬件总是隐式同步但在真正的并行性到来时开始以随机方式失败)。

Anyway I wouldn't try to read too much into the formal specification: from a purely formal point of view, I think it would be hard to say that an implementation in which a thread is never actually started is not conforming (proving black-box it's not conforming would require waiting till the end of time).无论如何,我不会尝试过多地阅读正式规范:从纯粹的正式角度来看,我认为很难说线程从未真正启动的实现不符合要求(证明黑盒它不符合需要等到时间结束)。 The same could be said of an implementation in which a+b takes 10 billion years...对于a+b需要 100 亿年的实现也可以这样说......

No it does not synchronize (scheduling etc...)不,它不同步(调度等...)

I usually use a condition variable construct to be really sure the asynchronous thread has really started.我通常使用条件变量构造来真正确定异步线程是否真正启动。 Like this:像这样:

#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();
}

For short duration functions I prefer using std::async though.对于持续时间较短的函数,我更喜欢使用 std::async。 Instead of creating a whole new thread (and taking up resources) it will get a thread from a threadpool它不会创建一个全新的线程(并占用资源),而是从线程池中获取一个线程

#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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM