我正在写一个游戏引擎(很有趣),并且有很多线程同时运行。我有一个类,该类将另一个类的实例作为私有变量保存,而该类又将另一个类的实例作为私有变量保存。我的问题是,我应该努力使线程安全中的哪一类?
我是否使所有线程都安全,并让每个线程都使用互斥锁保护它们的数据,是否仅使其中一个线程安全,并假定使用我的代码的任何人都必须了解,如果您使用的是基础类他们天生不是线程安全的。
示例:
class A {
private:
B b;
}
class B {
private:
C c;
}
class C {
// data
}
我知道我需要每个类的数据,以避免被数据争用破坏,但是我想避免在每个类的每个方法上抛出大量互斥量。我不确定正确的约定是什么。
答案 0 :(得分:1)
几乎可以肯定,您不想尝试使每个类都具有线程安全性,因为这样做最终会导致效率非常低(无用的互斥锁的许多不必要的锁定和解锁,毫无益处)并容易产生死锁(一次锁定的互斥锁越多,您越有可能具有不同的线程以不同的顺序锁定互斥锁的序列,这是死锁的进入条件,因此您的程序冻结了)。
如果弄清楚哪些线程需要访问哪些数据结构,您要做什么。在设计数据结构时,您希望尝试以尽可能减少线程之间共享的数据量的方式来设计它们-如果可以将其减少为零,则无需进行任何序列化完全没有! (您可能无法解决这个问题,但是如果您进行CSP/message-passing设计,您可能会非常接近,因为您唯一需要锁定的互斥锁就是保护消息传递队列的互斥锁)
还请记住,您的互斥对象不仅用于“保护数据”,而且还允许线程进行一系列更改,从可能访问该数据的其他线程的角度来看,这似乎是原子的。也就是说,如果线程#1需要对对象A,B和C进行更改,并且所有这三个对象都有各自的互斥体,则线程#1在修改对象之前锁定,然后在之后解锁,您仍然可以处于竞争状态,因为线程2可能“看到”更新已完成一半(即线程2可能在更新A之后但在更新B和C之前检查对象)。因此,通常需要将互斥锁提升到一个级别,使它们可以一次性覆盖您可能需要更改的所有对象-在ABC示例中,这意味着您可能希望拥有一个用于序列化访问的互斥锁到A,B和C。
一种解决方法是从整个程序的单个全局互斥锁开始-随时随地 any 线程需要读取或写入 any 数据结构其他线程可以访问它,即它锁定(然后解锁)的互斥锁。这种设计可能不会很有效(因为线程可能要花很多时间等待互斥体),但是它绝对不会遇到死锁问题。然后,一旦工作完成,您就可以查看单个互斥锁是否实际上对您来说是一个明显的性能瓶颈-如果不是,则完成您的程序,然后交付程序:) OTOH如果它是瓶颈,则可以进行分析您的哪些数据结构在逻辑上相互独立,并将全局互斥锁分为两个互斥锁-一个用于串行化对数据结构子集A的访问,另一个用于串行化对子集B的访问。(请注意,这些子集不大小不必相等-子集B可能只包含一个对性能至关重要的特定数据结构)根据需要重复操作,直到您对性能满意,或者您的程序开始变得过于复杂或有错误(在这种情况下)您可能需要再次拨回互斥锁粒度以恢复理智。)