首页 > 解决方案 > 与私有变量的其他类之间的类之间的线程安全

问题描述

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

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

例子:

class A {
private:
     B b;
}

class B {
private:
    C c;
}

class C {
 // data
}

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

标签: c++multithreadingoopclass-design

解决方案


您几乎肯定不想尝试使每个类都成为线程安全的,因为这样做最终会变得非常低效(有很多不必要的锁定和解锁互斥锁而没有任何好处)并且还容易出现死锁(您使用的互斥锁越多必须立即锁定,您就越有可能让不同的线程以不同的顺序锁定互斥锁序列,这是死锁的进入条件,因此您的程序会冻结您)。

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

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

解决它的一种方法是从整个程序的单个全局互斥锁开始 - 任何时候任何线程需要读取或写入任何其他线程可以访问的数据结构,即它锁定的互斥锁(然后解锁)。这种设计可能不会很有效(因为线程可能会花费大量时间等待互斥锁),但它绝对不会遇到死锁问题。然后,一旦你开始工作,你可以看看那个单一的互斥锁对你来说是否真的是一个明显的性能瓶颈——如果不是,你就完成了,发布你的程序:) OTOH 如果它是一个瓶颈,你可以分析您的哪些数据结构在逻辑上彼此独立,并将您的全局互斥锁分成两个互斥锁——一个用于序列化对数据结构的子集 A 的访问,另一个用于序列化对子集 B 的访问。(请注意,子集不'


推荐阅读