代码之家  ›  专栏  ›  技术社区  ›  migajek

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

  •  6
  • migajek  · 技术社区  · 14 年前

    到目前为止,我认为在“共享”对象(多线程通用)上执行的任何操作都必须使用“同步”进行保护,不管是什么。显然,我错了——在我最近研究的代码中,有很多类(线程安全类,如作者所说),其中只有一个类对几乎所有方法都使用关键部分。

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

    到目前为止,我还没有发现任何有趣的解释/文章/博客注释,所有的谷歌结果都是:

    a)线程和GUI之间的同步示例。从简单的ProgressBar到最复杂的,但仍然有一个很明显的教训:每次访问/修改GUI组件的属性时,都要在“同步”中进行。但再也没有了。

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

    c)非常简单的线程安全类(线程安全堆栈或列表)的例子——它们都是这样做的——实现锁/解锁方法,这些方法可以进入/离开关键部分并返回锁上的实际堆栈/列表指针。

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

    可以是代码形式;)但请不要再向我提供一个“使用同步更新进度条”…;)

    谢谢您!

    5 回复  |  直到 14 年前
        1
  •  5
  •   jachguate    14 年前

    你在问一个非常普遍的问题的具体答案。

    基本上,除了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字段进行操作的方法。在使用同步机制保护方法之前,这些方法不是线程安全的。它可以是用于值函数的多个eReaderExclusiveWritersIntronizer,以及用于inc和dec方法的CriticalSection。

    threadinc和threadec方法在threadValue变量上操作,threadValue变量定义为threadvar,因此我认为它是threadSafe,因为线程之间不共享它们访问的内存…来自不同线程的每个调用将访问不同的内存地址。

    如果您知道,根据设计,一个类应该只在一个线程中使用,或者在其他同步机制中使用,那么您可以自由地认为该线程是安全的。 按设计 .

    如果你想要更具体的答案,我建议你试着回答一个更具体的问题。

    最好的问候。

    编辑: 也许有人说整数字段是一个坏例子,因为您可以考虑在Intel/Windows上使用原子整数操作,因此不需要保护它…但我希望你明白这个想法。

        2
  •  2
  •   kludg    14 年前

    您误解了thread.synchronize方法。

    tthread.synchronize和tthread.queue方法在主(GUI)线程的上下文中执行受保护的代码。这就是为什么您应该使用Syncronize或Queue来更新GUI控件(如ProgressBar)-通常只有主线程才能访问GUI控件。

    关键部分是不同的-受保护的代码是在获取关键部分的线程的上下文中执行的,在前一个线程释放关键部分之前,不允许其他线程获取关键部分。

        3
  •  2
  •   himself    14 年前

    如果需要一组特定的对象进行原子更新,可以使用critical部分。这意味着,它们必须在任何时候都已完全更新或尚未更新。在过渡状态下,绝不能接触到它们。

    例如,对于一个简单的整数读/写,情况并非如此。读取整数的操作和写入整数的操作已经是原子的:您不能在写入整数的处理器中间读取整数,半更新。它不是旧的价值就是新的价值。

    但是如果你想的话 增量 整数是原子的,您没有一个,但必须同时执行三个操作:将旧值读取到处理器的缓存中,将其递增,然后将其写回内存。每一个操作都是原子操作,但它们三个在一起却不是。

    一个线程可能读取旧值(例如,200),在缓存中将其递增5,同时另一个线程也可能读取该值(仍然是200)。然后第一个线程写回205,而第二个线程将其缓存值200增加到203,然后写回203,覆盖205。两个增量(+5和+3)的结果应该是208,但由于操作的非原子性,结果是203。

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

    1. 变量、变量集或任何资源都是从多个线程使用的,需要原子更新。
    2. 它本身不是原子的(例如,调用由函数体内部关键部分保护的函数,已经是原子操作了)
        5
  •  0
  •   Misha    14 年前

    如果使用消息在线程之间进行通信,那么基本上可以完全忽略同步原语,因为每个线程只访问其内部结构和消息本身。本质上,这比使用同步原语要简单得多,而且可扩展性更高。