【问题标题】:Thread safety among classes with other classes for private variables与私有变量的其他类之间的类之间的线程安全
【发布时间】:2018-11-02 02:54:49
【问题描述】:

我正在编写一个游戏引擎(为了好玩),并且有很多线程同时运行。我有一个类,它将另一个类的实例作为私有变量保存,而另一个类的实例又将另一个类的实例保存为私有变量。我的问题是,我应该努力使这些类中的哪一个线程安全?

我是否让它们都成为线程安全的,并让它们中的每一个都使用互斥锁保护他们的数据,我是否只让其中一个成为线程安全的,并假设任何使用我的代码的人都必须明白,如果你使用的是底层类它们本质上不是线程安全的。

例子:

class A {
private:
     B b;
}

class B {
private:
    C c;
}

class C {
 // data
}

我知道我需要每个类的数据来避免因数据竞争而被破坏,但是我想避免在每个类的每个方法上抛出大量互斥锁。我不确定正确的约定是什么。

【问题讨论】:

  • 视情况而定。这是一个有用的技术,您可以尝试。 stackoverflow.com/questions/39185420/…
  • 感谢@Galik 的回复,我会考虑到这一点,我对使用C++17 有点谨慎,我知道很多人还没有采用这个标准。
  • 答案可能不清楚,但代码是C++14。只是底部的注释是针对C++17 用户的。

标签: c++ multithreading oop class-design


【解决方案1】:

您几乎肯定不想尝试使 每个 类都是线程安全的,因为这样做最终会变得非常低效(对互斥体进行大量不必要的锁定和解锁无济于事)并且还容易出现死锁(您必须一次锁定的互斥锁越多,您就越有可能让不同的线程以不同的顺序锁定互斥锁序列,这是死锁的进入条件,因此您的程序会冻结在您身上)。

如果确定哪些数据结构需要由哪些线程访问,您想要做什么。在设计数据结构时,您希望尝试以这样一种方式设计它们,即线程之间共享的数据量尽可能少——如果您可以将其减少到零,那么您就不需要进行任何序列化一点也不! (您可能无法做到这一点,但如果您进行 CSP/message-passing 设计,您可以非常接近,因为您需要锁定的唯一互斥锁是保护您的消息传递队列的互斥锁)

还请记住,您的互斥锁不仅是为了“保护数据”,而且还允许线程进行一系列更改,从可能访问该数据的其他线程的角度来看,这似乎是原子的。也就是说,如果您的线程 #1 需要对对象 A、B 和 C 进行更改,并且所有这三个对象都有自己的互斥锁,线程 #1 在修改对象之前锁定,然后再解锁,您仍然可以有一个竞争条件,因为线程#2 可能“看到”更新半完成(即线程#2 可能会在您更新 A 之后但在您更新 B 和 C 之前检查对象)。因此,您通常需要将互斥锁提升到一个级别,使其覆盖您可能需要一次性更改的所有对象——在 ABC 示例案例中,这意味着您可能希望拥有一个用于序列化访问的互斥锁给 A、B 和 C。

一种解决方法是从整个程序的单个全局互斥锁开始 -- 任何时候 any 线程需要读取或写入 any 数据结构其他线程可以访问,即它锁定的互斥锁(然后解锁)。这种设计可能不会很有效(因为线程可能会花费大量时间等待互斥锁),但它绝对不会遇到死锁问题。然后,一旦您开始工作,您可以查看单个互斥锁是否对您来说实际上是一个明显的性能瓶颈——如果不是,您就完成了,发布您的程序 :) OTOH 如果它是一个瓶颈,您可以然后分析您的哪些数据结构在逻辑上彼此独立,并将您的全局互斥锁分成两个互斥锁——一个用于序列化对数据结构的子集 A 的访问,另一个用于序列化对子集 B 的访问。(请注意,子集不'不需要大小相等——子集 B 可能只包含一个对性能至关重要的特定数据结构)根据需要重复,直到您对性能感到满意,或者您的程序开始变得过于复杂或错误(在这种情况下)您可能想再次拨回 mutex-granularity 以恢复理智)。

【讨论】:

    猜你喜欢
    • 2013-06-13
    • 1970-01-01
    • 2018-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-08
    • 2021-07-03
    相关资源
    最近更新 更多