【问题标题】:What is the definition of a thread safe function according to the C++11 (Language/Library) Standard?根据 C++11(语言/库)标准,线程安全函数的定义是什么?
【发布时间】:2021-07-12 13:56:36
【问题描述】:

TL;DR:说特定函数是“线程安全”的,因为同时调用两个可能不同的函数会发生数据竞争/em> 函数?这个问题在人们告诉“const 意味着/暗示 C++11 中的线程安全”[1][2]


考虑以下示例:

class X {
    int x, y; // are some more complex type (not supported by `std::atomic`)
    std::mutex m;
public:
    void set_x (int new_x) {x = new_x;} // no mutex
    void get_x () const {return x;}

    void set_y (int new_y) {
        std::lock_guard<std::mutex> guard(m); // guard setter with mutex
        y = new_y;
    }
    void get_y () const {return y;}
}

set_x 线程安全吗?

当然,set_x 不是线程安全的,因为同时从两个线程调用它会导致数据争用。

get_x、get_y 和 set_y 线程安全吗?

存在两种可能的推理:

  1. 是的,它们是线程安全的,因为同时从两个线程调用 get_x/get_y/set_y 不会导致数据争用。
  2. 不,它们不是线程安全的,因为从两个线程同时调用get_x(或get_y)和set_x(或set_y)会导致数据争用。

这三个函数的正确推理是哪一个?


问题总结

哪种推理是正确的?

  1. 一个函数是线程安全的,如果同时从两个线程调用它不会导致数据竞争。可以为set_x/get_x 工作,但对set_y/get_y 无效,因为这将得出结论set_y 和get_y 是线程安全的,但类Y 不是同时从两个线程调用 set_y 和 get_y 会导致数据争用。
  2. 一个函数是线程安全的,当且仅当它不访问任何可以在没有另一个函数内部同步的情况下修改的内存时。在我看来,这似乎是最一致的选项,但不是经常使用的方式(参见相关线程)。

相关话题

请注意,我已阅读以下相关主题:

【问题讨论】:

  • 考虑使用std::atomic&lt;int&gt; x; 而不是int x; 来实现线程安全。
  • @RichardCritten,你是绝对正确的,但这不足以确保线程安全。用于操作共享变量 x 的方法必须采用同步方式。
  • @ManifestMan 对于std::atomic&lt;T&gt;::load 和std::atomic&lt;T&gt;::store 两种方法,默认的内存排序值为std::memory_order_seq_cst。无需在代码中添加不必要的混乱。在深处,std::atomic 通过在两个不同级别上表现得像栅栏/屏障来实现其目的:在编译器级别,内存操作不会跨屏障重新排序;在机器指令级别,发出必要的架构内存屏障指令,具体取决于 CPU 架构和调用的std::atomic 方法。 (续...)
  • @ManifestMan (...cont'd) 请注意,std::atomic 的设计似乎有助于将其用作信号量;也就是说,在一些其他数据已完全写入(由该writer-core)之后由一个核心设置的标志变量;以及一个标志变量,在开始对其他数据的读取操作之前,其他内核将检查该变量。就 x86 和 ARM 而言,std::atomic 的当前 C++ 库实现足以保证正确操作,即使 那些其他数据 不是使用特殊方法写入或读取的。 (续...)

标签: c++ multithreading c++11 thread-safety


【解决方案1】:

C++ 标准不使用“线程安全”等术语;它使用更具体的语言。人类使用诸如“线程安全”之类的术语是因为我们发现它们很有用。

线程安全函数的常见概念是,当调用该函数时,假设没有其他人搞砸,不会造成数据竞争。 get_x 是线程安全的;在所有条件相同的情况下,您可以从任意数量的线程调用它并获得合理的结果。即使它不能与set_x 同时调用也是如此,因为这会导致数据竞争。但是数据竞争的原因是你调用了一个非线程安全的函数:set_x。

将函数归类为“线程安全”与否的重点是归咎于责任。如果您只调用“线程安全”函数,那么您的代码就是“线程安全”的。如果你偏离了“线程安全”函数的边界,那么正是你偏离了这些边界导致了数据竞争。

并发的set_x 调用导致数据争用不是get_x 的错。

至于get/set_y 的问题,如前所述,“线程安全”不是一个计算术语,也不是一个严格标准的术语。这是一个人类术语,是计算现实的简化。

什么是“线程安全”的规则基本上是“您可以同时调用任何线程安全函数和任何其他线程安全函数”。如果您不能同时调用get_y 和set_y,那么它们就不是“线程安全的”。

从严谨的角度来看,描述这两个函数的准确方式是set_y与同一对象上对set_y的其他调用同步,get_y与同一对象上对get_y的其他调用同步.我们也没有说它们彼此同步这一事实告诉您您需要知道什么。

从简化的角度来看,set_y 是“线程安全的”; get_y 不是。但是你也可以说get_y 是“线程安全的”而set_y 不是。这并不重要,因为它只是一个简化。

你是否声明 get_y const 并不是让它“线程安全”的原因。 Sutter 是说,如果你编写了一个 const 函数,你的工作就是以一种“线程安全”的方式来完成它。因此,get_y 被破坏了,因为它不是以线程安全的方式编写的,因为它不能被其他线程安全的函数以线程安全的方式调用。

【讨论】:

  • 我知道我有责任使 const 函数按位 const 或内部同步。这就是我使用“暗示”一词的方式。
  • 根据 C++11 标准,get_y 被破坏是否正确?如果我解释正确,它只是告诉我们只调用 const 函数应该是线程安全的。
  • @m7913d: "根据 C++11 标准,get_y 被破坏是否正确?" 没有要求用户编写一个函数是“线程安全的”。只要求不要编写不会引入数据竞争的程序。
  • 那么,get_y 坏了是什么意思?根据人类对线程安全的解释,它是被破坏的,还是根据 C++ 标准被破坏,因为它引入了数据竞争?
  • 我同意,我应该写(以避免任何混淆):“如果我解释正确,它只是告诉我们只调用 const 函数不应该引入数据竞争。”
【解决方案2】:

请注意,这是我自己根据自己的研究得出的自以为是的答案,并提供了其他人的意见。

线程安全函数的定义是什么?

一个函数是线程安全的,如果它不访问(读取或写入)任何可以被另一个函数在没有内部同步的情况下修改的内存:只有set_y是线程安全的。

请注意,线程安全不是由 C++ 标准明确定义的,它使用术语数据竞争。有关更多信息,请参阅 Nicol Bolas 的回答:线程安全并不总是黑白分明。

const 函数意味着 thread-safe 按位 const 或 内部同步

线程安全这个术语在“const 函数意味着线程安全”的上下文中被滥用。

“const 函数意味着线程安全”的意思是,从多个线程调用 const 函数应该是安全的(没有调用非const 函数同时在另一个线程中)。

正如Herb Sutter (29:43) 所说,在这种情况下,线程安全意味着按位 const 或内部同步,如果其他非常量函数不是真正的线程安全可以同时调用。

【讨论】:

    【解决方案3】:

    我也会同意你的以下陈述:

    如果一个函数不访问任何可以在没有另一个函数内部同步的情况下修改的内存,那么它就是线程安全的。在我看来,这似乎是最一致的选择,但不是经常使用的方式。

    只要共享变量是原子的,并且正确使用互斥锁来实现同步,我看你上面的说法没有任何问题。

    【讨论】:

    猜你喜欢
    • 2013-11-27
    • 2013-02-28
    • 1970-01-01
    • 1970-01-01
    • 2020-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多