Delphi线程 - 需要保护/同步哪些代码部分?

时间:2010-10-05 23:02:52

标签: multithreading delphi

到目前为止,我认为对“共享”对象(多个线程通用)所做的任何操作都必须使用“synchronize”进行保护,无论如何。显然,我错了 - 在我最近学习的代码中有很多类(线程安全的,如作者声称的那样),并且其中只有一个使用Critical Section来处理几乎所有的方法。

如何找到需要使用CriticalSection(或任何其他方法)保护我的代码的哪些部分/方法?哪些不是?

到目前为止,我没有偶然发现任何有趣的解释/文章/博客说明,所有谷歌搜索结果都是:

a)线程和GUI之间的同步示例。从简单的进度条到最复杂的,但仍然很明显:每次访问/修改GUI组件的属性时,请在“同步”中执行此操作。但仅此而已。

b)解释关键部分,互斥体等的文章。只是一种不同的保护/同步方法。

c)非常非常简单的线程安全类(线程安全堆栈或列表)的示例 - 它们都是相同的 - 实现锁定/解锁方法,它们进入/离开临界区并在锁定时返回实际的堆栈/列表指针

现在我正在寻找解释应保护代码的哪些部分

可能采用代码形式;)但请不要再向我提供“使用Synchronize更新进度条”......;)

谢谢你!

5 个答案:

答案 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,但是由于非 - 运营原理。

因此,您在以下情况下使用关键部分:

  1. 变量,变量集或任何资源都来自多个线程,需要原子更新。
  2. 它本身不是原子的(例如,调用函数体内部关键部分保护的函数,已经是原子操作)

答案 3 :(得分:1)

答案 4 :(得分:0)

如果您使用消息传递线程之间进行通信,那么您基本上可以完全忽略同步原语,因为每个线程只访问其内部结构和消息本身。从本质上讲,这比使用同步原语更容易,也更具可扩展性。