.NET C#多线程

8

关于线程的问题,满足我的好奇心...

假设我有一个静态变量 _status(ProgressStatus),许多线程都从中读取。 为了更新这个静态变量,我使用一个不可变对象 ProgressStatus,创建一个新实例,然后交换引用。

var status = new ProgressStatus (50, "Working on it"); //plus many more fields in constructor

lock (_statusLocker) _status = status; // Very brief lock

这是读取器的代码。
public GetProgressStatus () {

     var status = new ProgressStatus (_status.ID, _status.Description); 
     return status }

如果我不应用锁定,最糟糕的情况是什么?


重要的是您向我们展示读取器代码。顺便说一下,对象的不可变性并不能解决所有问题,分配引用是原子的也不行。如果在操作过程中另一个线程更改了引用Console.WriteLine(status.Progress + ", " + status.Text)仍然可能产生类似于"50, Complete"的结果。我想您必须决定这种不一致是否可以接受。 - Ani
好的观点。当我编写读取器代码时,我也意识到了这一点 - 哈哈 - Mark 909
3个回答

7

其他线程可能无法看到新值。

实际上,除非它们也在锁定,否则它们仍然可能无法看到新值。

即使引用以原子方式更新(即它永远不会是旧值和新值的混合值),这并不意味着其他线程何时可以看到更改 - 或者其他线程甚至会在何时检查。 (例如,一个线程可能已经将值缓存到寄存器中,而没有任何指示该线程需要检查主内存,它可能不会这样做。)

通过将变量声明为volatile,您可能会避免这种情况 - 但老实说,我已经停止相信我完全理解volatile的含义了。

在使用共享可变数据(读取或写入)时始终使用锁定可以消除此问题,因为锁定的语义。以无锁且保证正确的方式进行操作涉及对正在发生的事情有更深入的了解。 (请注意,这里可变的是变量,即使它所引用的对象不是。)


@thecoop:是的。我曾经认为对易失性变量的写操作会立即传输到主内存,而读操作总是从主内存中获取,但实际上并不是那么简单。我感到非常困惑:( 我认为最好还是交给专家来处理。 - Jon Skeet
在我的代码中,我实际上正在尝试从一个字典对象中读取。因此,可能我的更新代码会在字典对象被读取时(由字典对象中的某些内部例程)输出该字典对象,并且我猜可能会产生意外的结果。 - Mark 909
@Mark 909:如果它只是一个普通的Dictionary<TKey, TValue>,那么它本身就不是线程安全的。 - Jon Skeet
@Mark 909:读取不会损坏 - 它只是指向新字典。如果您想从单个字典中读取多个值,应将引用复制到本地变量中,并通过该变量重复读取。 - Jon Skeet
既然读取将持续不断地进行,而写入只会在一个月内进行一次,那么我猜应该没问题。 - Mark 909
显示剩余8条评论

0

如果你的程序依赖于这个值按可预测的方式更新,那么可能会出现竞态条件。

以下是一个很好的“无锁”函数:

static void LockFreeUpdate<T>(ref T field, Func<T, T> updateFunction)
    where T : class
{
    var spinWait = new SpinWait();
    while (true)
    {
        T snapshot1 = field;
        T calc = updateFunction(snapshot1);
        T snapshot2 = Interlocked.CompareExchange(ref field, calc, snapshot1);
        if (snapshot1 == snapshot2) return;
        spinWait.SpinOnce();
    }
}

我不能拿到功劳,我在网上找到的

http://www.albahari.com/threading/part5.aspx#_SpinLock_and_SpinWait


-1

在这种情况下,你不应该需要锁,因为更改引用是一个原子操作。


我不明白为什么这个评论被标记了,有人能解释一下吗?在我看来很合理。 - Mark 909
@Mark 909:因为正如我在评论中所解释的那样,它是不正确的。仅仅因为更新是原子性的,并不意味着锁定(或其他内存屏障)不是必需的。 - Jon Skeet
2
更具体地说,如果_status没有被标记为volatile,其他线程可能不会立即看到值的变化。当我编写答案时,我已经假定它已经是volatile了,这并不是我做过的最聪明的假设。无论如何,根据Jon Skeet的回答,volatile甚至可能无法实现其所记录的内容。 - ShZ

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