我有一个对象x,需要从几个(5个以上的线程)访问。对象的结构是
Tx = class
private
Fx: integer;
public
property x: integer read Fx read Fx;
etc;
更好(最优雅)的保护方式是什么:
A)
Tx = class
private
Fx: integer;
public
property x: integer read Fx read Fx;
public
constructor Create; <--- Create the criticalsection here
destructor Destroy; <--destroy it here
etc;
var
cs: TCriticalSection;
Obj: Tx;
function GetSafeObject(): Tx;
begin
CS.Enter;
try
Result:= Obj;
finally
CS.Leave;
端;
并始终将对象作为GetSafeObj()。x:= 3;
访问或
Tx = class
private
Fx: integer;
FCS: TCriticalSection;
public
property x: integer read GerX read SetX;
public
constructor Create; <--- Create the criticalsection here
destructor Destroy; <--destroy it here
etc;
where
function Tx.Getx(): integer;
begin
CS.Enter;
try
Result:= Fx;
finally
CS.Leave;
end;
end;
端;
并始终正常访问该对象。我猜第一个选项更优雅,即使两种方法都能正常工作。你评论?
答案 0 :(得分:7)
转到选项B,使关键部分在对象内部。如果该类的用户必须使用外部函数来安全地访问该实例,则不可避免地会有人不会和房子一起倒塌。
您还需要考虑您希望保护哪些操作语义免受多个并发读取的影响。写道。如果在getter和setter中放置一个锁,则可以保证对象内部一致,但对象的用户可能会看到多线程工件。例如,如果线程A将10写入对象的属性,而线程B将50写入同一对象的该属性,则只有其中一个可以是最后一个。如果A恰好先行,则A将观察到他们给房子写了一张10,但是当他们再次阅读时,他们看到B的50在那里读到了A的写后读写间隙。
另请注意,您并不需要锁来保护单个整数字段。对齐指针大小的整数写入是当今几乎所有硬件系统上的原子操作。你肯定需要一个锁来保护多块数据,比如结构或多步操作,比如同时更改两个相关字段。
如果有任何方法可以重新设计你的设计,使这些对象在线程上的特定操作中是本地的,那就去做吧。制作本地数据副本可能会略微增加内存占用,但它可以大大简化多线程代码,并且比在整个应用程序中留下互斥锁地雷运行得更快。寻找其他简化假设 - 如果您可以设置系统以使对象在多个线程可用时是不可变的,那么该对象根本不需要任何锁定保护。只读数据适合跨线程共享。非常非常好。
答案 1 :(得分:6)
使CS成为对象的成员并使用属性getter / setter方法中的CS是正确的方法。另一种方法不起作用,因为它在实际访问对象之前锁定和解锁CS,因此属性值根本不受保护。
答案 2 :(得分:5)
一种简单的方法是在对象周围使用一个线程安全的包装器,类似于TThreadList
。包装器需要两个方法:Lock
(进入临界区并返回内部对象)和Unlock
(离开临界区)。