【问题标题】:C#: Question regarding thread safety for member variablesC#:关于成员变量的线程安全的问题
【发布时间】:2010-11-25 17:16:47
【问题描述】:

有人能解释一下为什么线程的例子总是让一个对象(特别是一个成员变量)被多个线程访问吗?

我的问题是,将成员变量设为静态意味着它将在类的所有其他实例之间共享。有时我发现我希望一个类中的多个线程“接触”成员变量,但同时允许每个对象拥有自己的副本。

执行我提到的操作的答案是否只是替换:

  • 使用 volatile 关键字
  • 使用锁(对象)

【问题讨论】:

    标签: c# multithreading synchronization


    【解决方案1】:

    没有什么要求成员变量是静态的线程。

    大多数示例都使用静态变量,特别是因为它们试图展示如何在两个单独的线程之间同步数据。如果你想这样做,你需要有一个两个线程都可以访问的值。静态变量是最简单的选择,因为它们在任何地方都可以访问。

    您可以轻松地将一个类的引用传递到您的线程中,并让该线程和您的主线程共享一个非静态成员变量。但是,您需要提供良好的锁定/同步机制,因为您将有两个线程共享一个类的一个实例。

    【讨论】:

    • 关键字 volatile 是否应该包含在 static 中?是否担心在一个线程中缓存静态值而不在另一个线程中获取最新值?
    • @Setheron:总是使用lock(object) 来访问多个线程共享的变量。这将为您提供独占访问权限,它会为您完成所有工作,因此所有线程始终获取最新值(内存屏障)。
    • 查看我对 volatile 使用的回答。在大多数实际情况下,您可以完全忽略它曾经存在过。 (尽管 volatile 大师可能会为 volatile 提供一个极好的案例,恕我直言,您将不需要它。)
    【解决方案2】:

    如果您希望每个线程使用相同的成员变量,但还维护一个单独的副本,那么这里就有矛盾了。他们要么使用相同的变量,要么不使用。除非我没有正确理解你。

    话虽如此,如果您确实需要多个线程访问一个静态字段,每个线程都维护自己的私有值,您可以使用ThreadStatic 属性。此属性确保静态字段对每个线程都是私有的(通常称为“线程本地”)。

    [ThreadStatic]
    private static bool s_threadHasDoneItsWork;
    

    请注意,您不能通过静态构造函数或直接将线程本地静态字段初始化为static type field = value。 (编译器不会抱怨,但它不会正常工作。)


    volatile 告诉运行时该字段(无论是否静态)必须始终直接从您的主内存访问,因此您不需要使用锁或内存屏障来同步线程和内核。可以这么说,它始终是最新的,而其他字段仍然可以等待您的处理器的内存缓存与您的主内存同步。

    但这正是它所做的限制:访问该特定字段。一旦你读过它,这个值又是陈旧的,所以永远不要考虑做类似 volatileField++ 的事情(这意味着“读取 volatileField,在你刚刚读取的值上加一个,设置 volatileField”,而不是“增加 volatileField”,您需要为此使用 Interlocked 类,这要贵得多)。

    使用 volatile 字段的一种安全方法是直接读取它们,但是当您修改它们时,请在读取或写入它们之前使用锁定机制。 (我能想到的唯一合理的例外是一个布尔标志,比如“我现在完成了”。)

    毫无疑问,volatile 字段的实际用途相当有限。坚持简单的锁定; lock 关键字和对应的Monitor 类的方法负责单用户内存同步(一旦你进入和退出锁)。

    【讨论】:

    • 是的,我在 MSDN 上看到了 Threadstatic 属性,它把我搞糊涂了。如果每个线程都维护自己的个人副本,这似乎违背了静态变量的定义。
    • 在某种程度上确实如此,是的。但是,如果您需要维护特定于线程的状态,或者声明从您的方法之一调用的方法需要能够访问,但没有其他方法,它会很有用。根据其用途,线程本地存储可以避免线程同步的需要(因为线程之间没有共享),所以它很有用。
    • 甚至没有意义,为什么它需要静态字段。单独的 Threadstatic 应该是有意义的。
    • 不,ThreadStatic 仅适用于静态字段。不过,这是 CLR 的限制。如果您需要在实例级别实现线程本地存储,则涉及更多,因为您永远无法预测会有多少实例,以及何时创建它们并收集垃圾。对于静态字段,您确实知道:现在只有一个,并且只有在线程死亡时才清理。
    • 我明白你的意思。虽然我会假设 VolatileField++ 是一个原子指令,如果该字段是原子的,它将是线程安全的。
    猜你喜欢
    • 1970-01-01
    • 2010-12-30
    • 2014-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-10
    • 2018-12-24
    • 1970-01-01
    相关资源
    最近更新 更多