类与私有变量的其他类之间的线程安全性

Thread safety among classes with other classes for private variables

本文关键字:之间 线程 安全性 其他 变量      更新时间:2023-10-16

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

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

示例:

class A {
private:
B b;
}
class B {
private:
C c;
}
class C {
// data
}

我知道我需要每个类的数据来避免因数据竞争而损坏,但我希望避免在每个类的每个方法上都抛出大量互斥。我不确定什么是合适的惯例。

您几乎肯定不想让每个类线程都是安全的,因为这样做最终会非常低效(大量不必要的互斥锁锁定和解锁毫无益处(,而且容易出现死锁(一次锁定的互斥锁越多,就越有可能让不同的线程以不同的顺序锁定互斥锁序列,这是死锁的进入条件,因此程序会冻结在你身上(。

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

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

一种方法是从整个程序的一个全局互斥开始——任何时候任何线程都需要读取或写入其他线程可以访问的任意数据结构,也就是它锁定(然后解锁(的互斥。这种设计可能效率不高(因为线程可能会花很多时间等待互斥(,但肯定不会出现死锁问题。然后,一旦你完成了这项工作,你可以看看这个互斥体是否真的是你的一个明显的性能瓶颈——如果不是,你就完成了,交付你的程序:(OTOH如果它是一个瓶颈,你可以分析你的哪些数据结构在逻辑上相互独立,并将全局互斥体拆分为两个互斥体——一个用于序列化对数据结构子集A的访问,另一个用于串行化对子集B的访问,或者你的程序开始变得过于复杂或有缺陷(在这种情况下,你可能想再次拨回互斥粒度一点,以恢复理智(。