【问题标题】:Sharing data during multi-threading - are non-static variables OK?在多线程期间共享数据 - 非静态变量可以吗?
【发布时间】:2014-11-06 12:12:00
【问题描述】:

在众所周知的Joseph Albahari's article on Threading中,多个线程使用的所有类变量都声明为static字段=>所有线程都可以访问同一个变量。这需要在所有读/写的地方配备lock()机制,工作就完成了。

我的问题是关于类属性的实现。我知道如果我使用 static 后备存储实现(例如)属性 Timeout 是有效的:

class MyClassWithWorkerThread {
    private readonly object _locker = new object();
    private static int _Timeout = false;
    private int Timeout {
        get {
            lock (_locker) {
                return _Timeout;
            }
        }
        set {
            lock (_locker) {
                _Timeout = value;
            }
        }
    }
}

这将使变量_Timeout 在所有类实例之间共享。

但在我的例子中,多线程处理是类实例私有的。它以New() 开头,以Dispose() 结尾。主线程和工作线程都访问Timeout 属性(但_Timeout 后备存储永远不会在属性getter/setter 之外访问)。

我不希望 _Timeout 值在应用程序范围内。我想让它对每个类实例都是唯一的。 我的问题是:我可以安全地从 _Timeout 变量中删除 static 以实现此目的吗?

注意:如果代码中有任何错误,我很抱歉,我实际上使用的是 VB.NET,并且我使用工具对其进行了转换。我希望主要问题仍然清楚。

【问题讨论】:

    标签: .net multithreading static shared


    【解决方案1】:

    绝对安全且非常推荐(静态变量即使在需要时也很难进行测试)。假设 safe 你也意味着 valid 你不必忘记的是:

    this.Timeout = 0; // This is safe and valid
    ++this.Timeout; // This is safe but not valid
    

    因为++ 运算符不是原子的(这就是我们有Interlocked 类的原因)。当然同样适用于这种情况:

    if (this.Timeout == 0)
        Timeout = 10;
    

    因为即使每次访问都是安全(我会说读取属性始终是安全的,但您可以读取没有lock 屏障的旧值)它不是原子操作,并且值可能会在之后更改测试和新任务之前。更复杂?

    if (this.Timeout == 0)
        Timeout = Timeout * 2;
    

    在这种情况下,您每次阅读Timeout 时可能会得到不同的值。出于这个原因,我说属性内的锁很少有用,除非它是只读属性。最好从属性 get/set 中删除该锁并将代码包装在 lock 语句中:

    lock (_locker) {
        if (this.Timeout == 0)
            Timeout = Timeout * 2;
    }
    

    另请注意,对于int _Timeout(我假设false 的分配只是一个错字)您可以简单地删除锁定并使其成为volatile

    private volatile int _Timeout;
    

    当然,这不会解决其他描述的问题,但它可能对只读属性有用(并且更快)(或者对于非常受控的情况,volatile 修饰符可能很棘手,并且它具有不同的含义比较使用 C,很容易忘记 访问 它们是原子的,仅此而已)。

    【讨论】:

    • 我完全同意使用您所描述的 lock() (VB: SyncLock ... End SyncLock) 块。实际上,我打算通过在需要的地方创建 Timeout 的本地副本(通过单次读取操作)来避免您突出显示的场景,因为在类方法中没有对其进行写入。我没有在问题中说清楚,但我理解锁定概念。但至少其他读者会从您的详细回答中受益,所以谢谢。
    • 易变字段非常原子的。显然按顺序执行两个操作不是原子的,但也不是按顺序锁定两次。
    • @Voo 你是对的,我的句子很不清楚:access 到 volatile 变量是原子的,但 似乎 执行原子操作的运算符不是(像 ++)。
    【解决方案2】:

    静态关键字是正交(即:独立或无关)线程。

    仅当您只需要该属性/字段/类/方法/等的单个实例时才应使用静态关键字。

    在您的情况下,如果您希望 _Timeout 的值对实例是唯一的,则不需要静态。

    我没有看过你链接的文章,但我想你可能误解了它(或者作者误解了静态的使用)

    【讨论】:

    • 虽然引用的文章非常好并且被很多人推荐,但我看到static 在那里有点过度使用,即使在不一定需要它的示例中也是如此。但是现在,这一点已经明确了,谢谢。
    • @miroxlav 是的,我猜他几乎在所有地方都使用静态来simplicity 并保持代码示例更短(无需创建和传递实例对象)。快速浏览一下,我认为这不是他建议的线程,只是一种方便的语法。
    猜你喜欢
    • 1970-01-01
    • 2011-06-23
    • 2016-05-19
    • 2013-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多