到目前为止,我认为对“共享”对象(多个线程通用)所做的任何操作都必须使用“synchronize”进行保护,无论如何。显然,我错了 - 在我最近学习的代码中有很多类(线程安全的,如作者声称的那样),并且其中只有一个使用Critical Section来处理几乎所有的方法。
如何找到需要使用CriticalSection(或任何其他方法)保护我的代码的哪些部分/方法?哪些不是?
到目前为止,我没有偶然发现任何有趣的解释/文章/博客说明,所有谷歌搜索结果都是:
a)线程和GUI之间的同步示例。从简单的进度条到最复杂的,但仍然很明显:每次访问/修改GUI组件的属性时,请在“同步”中执行此操作。但仅此而已。
b)解释关键部分,互斥体等的文章。只是一种不同的保护/同步方法。
c)非常非常简单的线程安全类(线程安全堆栈或列表)的示例 - 它们都是相同的 - 实现锁定/解锁方法,它们进入/离开临界区并在锁定时返回实际的堆栈/列表指针
现在我正在寻找解释应保护代码的哪些部分。
可能采用代码形式;)但请不要再向我提供“使用Synchronize更新进度条”......;)
谢谢你!答案 0 :(得分:5)
您要求对一般问题给出具体答案。
基本上,除了UI操作之外,您应该保护每个共享内存/资源访问,以避免两个潜在的竞争线程:
通常,我认为任何其他操作线程都是安全的,包括访问非共享内存或不访问共享对象的操作。
例如,考虑这个对象:
type
TThrdExample = class
private
FValue: Integer;
public
procedure Inc;
procedure Dec;
function Value: Integer;
procedure ThreadInc;
procedure ThreadDec;
function ThreadValue: Integer;
end;
ThreadVar
ThreadValue: Integer;
Inc,Dec和Value是在FValue字段上运行的方法。在使用某种同步机制保护方法之前,这些方法不是线程安全的。它可以是Value函数的MultipleReaderExclusiveWriterSinchronizer和Inc和Dec方法的CriticalSection。
ThreadInc和ThreadDec方法对ThreadValue变量进行操作,该变量定义为ThreadVar,因此我认为它是ThreadSafe,因为它们访问的内存不在线程之间共享......来自不同线程的每次调用都将访问不同的内存地址。
如果你知道,按照设计,一个类只应该在一个线程或其他同步机制中使用,你可以自由地考虑线程安全的设计。
如果您想要更具体的答案,我建议您尝试更具体的问题。
最好的问候。
编辑:也许有人说整数字段是一个不好的例子,因为你可以考虑整数运算原子在英特尔/ Windows上因此不需要保护它...但我希望你明白这个想法
答案 1 :(得分:2)
你误解了TThread.Synchronize方法。
TThread.Synchronize和TThread.Queue方法在主(GUI)线程的上下文中执行受保护的代码。这就是为什么你应该使用Syncronize或Queue来更新GUI控件(比如progressbar) - 通常只有主线程才能访问GUI控件。
关键部分是不同的 - 受保护的代码在获取关键部分的线程的上下文中执行,并且在前一个线程释放它之前,不允许其他线程获取关键部分。
答案 2 :(得分:2)
如果需要以原子方式更新某组对象,则使用临界区。这意味着,它们必须始终已经完全更新或者根本没有更新。它们绝不能在过渡时期进入。
例如,对于简单的整数读/写,情况并非如此。读取整数的操作以及写入它的操作已经是原子的:你不能在处理器写入它的过程中读取整数,半更新。它总是旧价值或新价值。
但是如果你想原子地递增整数,那么你不必一次,而是必须同时做三个操作:将旧值读入处理器的缓存,增加它,然后将其写回记忆。每个操作都是原子操作,但它们中的三个不是。
一个线程可能会读取旧值(例如,200),在缓存中将其递增5,同时另一个线程也可能读取该值(仍为200)。然后第一个线程写回205,而第二个线程将其缓存值200增加到203并写回203,覆盖205.两个增量(+5和+3)的结果应该是208,但是由于非 - 运营原理。
因此,您在以下情况下使用关键部分:
答案 3 :(得分:1)
答案 4 :(得分:0)
如果您使用消息传递线程之间进行通信,那么您基本上可以完全忽略同步原语,因为每个线程只访问其内部结构和消息本身。从本质上讲,这比使用同步原语更容易,也更具可扩展性。