【问题标题】:std::lock_guard constructor instantiation orderstd::lock_guard 构造函数实例化顺序
【发布时间】:2018-07-08 19:19:01
【问题描述】:

我想这是一个关于左值实例化排序的更一般的问题。

简而言之,这样安全吗?:

void func1()
{
    std::lock_guard< std::mutex > lock( mutex );
    //do some stuff in locked context
}

void func2()
{
    func1();
    std::lock_guard< std::mutex > lock( mutex );
    //do some stuff in locked context
}

我有点担心编译器可能会在从 func2 中调用 func1 之前调用 lock_guard 的构造函数,从而导致死锁。

是否保证这是安全的还是我需要做这样的事情:

void func1()
{
    std::lock_guard< std::mutex > lock( mutex );
    //do some stuff in locked context
}

void func2()
{
    func1();

    { //lock
        std::lock_guard< std::mutex > lock( mutex );
        //do some stuff in locked context
    } //unlock
}

【问题讨论】:

  • 您担心的依据是什么?
  • 仅凭经验,而不是标准中的空话,我认为它是完全安全的。
  • 在没有循环的情况下,执行是纯自上而下的。这包括对象构造。在这两个 sn-ps 中,func1 总是会在对象 lock 被构造之前被调用。换句话说,它是安全的。在这两种情况下。
  • 我不是编译器/优化器如何运行的专家,所以我不确定编译器是否会首先实例化函数中的所有对象,然后开始执行函数,或者它是否会在我编写代码的顺序。显然,函数调用是按照我放置它们的顺序发生的,但是在某些情况下,我可以看到编译器/优化器将左值实例化移动到函数的顶部。我不怀疑它确实有很多人可能会遇到麻烦,我只是想确保这不会发生。
  • 如果您不是专家,通常根本不应该考虑编译器。只需遵循语言规则,并相信您的工具通常是实用且有效的。

标签: c++ c++11 locking language-lawyer


【解决方案1】:

您所描述的那些东西(函数调用和锁的实例化)在标准中被称为完整表达式。

根据C++11 1.9 Program execution /14C++14 中的相同位置和文本,C++17 4.6 Program execution /16 中的相同文本):

与完整表达式关联的每个值计算和副作用都在与要评估的下一个完整表达式关联的每个值计算和副作用之前排序。

种情况下,看似顺序的操作可以被不确定地排序,但这不是其中之一。


顺便说一句,如果您担心单个执行线程可能会尝试重新获取相同的互斥锁两次的可能性,那么您可能会发现recursive_mutex 会派上用场。


另外进一步,在您询问 C++98 和 C++03 的评论中,线程仅在 C++11 中引入。在此之前,C++ 仍然沿用 C 中序列点的概念。

C++98 1.9 Program execution /16C++03 1.9 Program execution /16 中,您会发现类似的措辞:

每个完整表达式的计算完成时都有一个序列点。

【讨论】:

  • 感谢 paxdiablo,这就是我一直在寻找的答案。
  • 顺便说一句,假设如果我实际使用 C++98 并实现自己的 lock_guards,我会假设这也适用于 C++98。
  • @Nathan,线程仅被引入 C++11。在此之前,它使用与 C 相同的措辞,即“序列点”。我已经更新了答案以包含此内容,但我不确定我是否可以自信地向您保证在相当古老的实现中添加类似的东西:-)
  • @paxdiablo 我只是想知道 "is sequenced" 是什么意思。考虑a++; b++;,其中abints。我的观点是编译器和CPU都可以重新排序对应的inc指令,这样b首先递增(如果它保持整个程序的效果)。
  • @Daniel,这些是完整的表达式,必须按顺序进行排序,标准对排序的含义进行了痛苦的详细说明:-) 考虑a++; a *= 2;。 C++“vm”不允许重新排序。底层硬件是否在标准范围内,只要效果符合您的预期即可。
【解决方案2】:

只是对@paxdiablo 的回答的一些补充说明。通常,在特定架构上执行生成的程序必须具有与根据 C++ 标准在源代码中编写的效果相同的效果。由于优化,编译器和 CPU 都可以以与源代码匹配的顺序不同的顺序生成和执行指令。

但是,当使用多线程同步结构(例如互斥锁和原子内存操作)时,不得发生这种重新排序(如果程序员没有明确放宽)。否则,多线程编程根本不可行。

编译器知道如果它看到这样的代码它不应该重新排序指令。在 CPU 级别上,此任务存在内存障碍。

因此,如果你锁定了一个互斥体,你可以确定上面写的代码已经完成了。这是通用的多线程概念,所以我坚信它也适用于 C++。如果我错了,请纠正我。

【讨论】:

    猜你喜欢
    • 2018-05-11
    • 1970-01-01
    • 2013-08-24
    • 1970-01-01
    • 2016-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-18
    相关资源
    最近更新 更多