在多线程期间共享数据-非静态变量可以吗?

3
在著名的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并使用工具进行转换。我希望主要问题仍然清晰明了。
2个回答

3

绝对安全并且非常推荐(即使需要使用静态变量进行测试也很痛苦)。假设您所说的“安全”也包括“有效”,那么您不应该忘记的是:

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 (_locker) {
    if (this.Timeout == 0)
        Timeout = Timeout * 2;
}

同时注意对于int _Timeout(我假设使用false进行赋值只是一个打字错误),你可以简单地删除锁定并将其设置为volatile
private volatile int _Timeout;

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

我完全同意您所描述的使用lock()(VB:SyncLock ... End SyncLock)块的做法。实际上,我打算通过创建Timeout的本地副本(通过单个读取操作)来避免您所强调的情况,因为类方法内部没有对其进行写入。虽然我在问题中没有表述清楚,但我理解锁定的概念。但至少其他读者将从您详细的答案中受益,所以谢谢您。 - miroxlav
易失性字段确实是非常原子的。显然,按顺序执行两个操作不是原子操作,但连续两次锁定也不是。 - Voo
@Voo 你说得对,我的句子不太清楚:对于 volatile 变量的访问是原子性的,但看起来执行原子操作的运算符并不是(比如 ++)。 - Adriano Repetti

1

静态关键字与线程无关。它只应在您想要该属性/字段/类/方法等的单个实例时使用。

如果您希望该值对实例唯一,则在_Timeout上不需要使用静态。

我没有阅读您链接的文章,但我认为您可能误解了它(或作者误解了静态的用法)。


虽然所提到的文章非常优秀,被许多人推荐,但我认为其中 static 的使用有些过度,甚至在不必要的示例中也使用了它。但现在,重点已经表达清楚了,谢谢。 - miroxlav
@miroxlav 是的,我想他几乎在所有地方都使用了 static 来保持代码简单,并使代码示例更短(无需创建和传递实例对象)。我快速查看了一下,我认为这并不是他建议用于线程的东西,只是一种方便的语法。 - Adriano Repetti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接