在 C# 中使用 Interlocked 获取和设置线程安全吗?

Posted

技术标签:

【中文标题】在 C# 中使用 Interlocked 获取和设置线程安全吗?【英文标题】:Is using Interlocked in C# get and set thread safe? 【发布时间】:2014-05-28 19:48:32 【问题描述】:

是否可以通过在属性访问器中使用Interlocked 来获得线程安全的属性?

例子:

public class A()

    private static long count;

    public static long Count
    
        get
        
            return Interlocked.Read(ref count);
        
        set
        
           Interlocked.Exchange(ref count, value); 
        
    

【问题讨论】:

“线程安全”是整个程序的属性,而不仅仅是其中的一个 sn-p,在您明确明确定义“线程安全”的含义之前,它也不是一个特别有意义的术语",表示在整个程序中将/不会满足哪些条件。话虽如此,我会说一般来说,看到这样的属性是代码气味。这对我来说是一个信号,有人认为某些东西会正常工作,而实际上它不会,尽管需要查看该属性的每一次使用才能确定。 关于示例代码的“线程安全”,唯一可以说的是,Count 的值是由属性集实际分配给它的值(如果您用简单的分配和返回替换联锁)。但是,正如 Servy 所说,如果您不准确定义您所谈论的“线程安全”属性,那并没有多大意义。 @MaMazav 你甚至不能这么说,真的。 Interlocked.Read 有可能返回一个值,将其存储在临时文件中,调用 setter,更改值,然后让 getter 返回,因此您甚至不能说 getter 不返回一个陈旧的价值。添加的内存屏障有助于它不会作为陈旧,并防止重新排序获取和设置来自同一线程,但是当涉及多个线程时,您仍然可以由于在返回值之前将值存储在临时(隐式完成)中,因此可以做一些非常时髦的事情。 原子性和线程安全是不同的概念,你从这段代码中得到的原子性保证是非常弱的。您所拥有的只是一个保证,当您的程序在 32 位模式下运行时,您将永远不会读取部分更新的值。 @Servy 我唯一的主张是他编写的代码承诺返回值必须是过去分配给它的值(而不是部分分配,如 HansPassant 所说)。我不明白如何将值存储在临时文件中。 【参考方案1】:

当上面的例子运行时,getset 访问器的执行行为是线性化的。在不使用Interlocked 的情况下,getset 访问器的执行行为介于弱一致性和顺序一致性之间(即只保证表现出弱一致性)。

当作为 64 位进程运行时,同样可以通过标记字段 volatile 并使用简单的返回语句和赋值运算符来完成。但是,当作为 32 位进程运行时,对 volatile 64 位字段的操作不能保证是原子的,因此需要使用 Interlocked 来确保原子性。

【讨论】:

那么...这是是还是否? @HenkHolterman 这个问题在这种情况下没有意义,因此不能简单地回答是或否。从外部代码与相关属性交互的角度来看,我的回答是准确的。 @280Z28 当一个问题由于缺乏信息而无法回答时,您应该投票关闭它,而不是给出一个不完整且无益的答案。 @280Z28 至此,您已经给出了一些合理的声明,说明此代码做出了哪些保证。问题是我们不知道这些保证是否真的足以满足 OP 的目的,不知道它们是什么。结合大多数人实际上并不理解您提到的“弱一致性”和“顺序一致性”等概念的事实,读者可以根据这个答案确定他们的代码是否有效的可能性似乎......在我心中低落。【参考方案2】:

它不是线程安全的。试试这个代码:

long i

    get  return Interlocked.Read(ref _i); 
    set  Interlocked.Exchange(ref _i, value); 



long _i;

void Main()


    Parallel.ForEach(Enumerable.Range(0, 1000_000), 
        //new ParallelOptions  MaxDegreeOfParallelism = 1,
        x=>
    
        i++;
    );

    i.Dump();

当您运行此代码时,答案不是 1000_000,而是稍微低一点,证明它不是线程安全的。不知道为什么会这样

【讨论】:

++ 后缀将捕获 i 的值并将其增加一。在 i 的值增加之前(在读取之后),多个线程可以安全地读取 i 的值。稍后这些不同的线程将“安全地”在 i 之上写入相同的值。

以上是关于在 C# 中使用 Interlocked 获取和设置线程安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 C# 中的枚举类型应用 InterLocked.Exchange?

SQL Server 中的 NEXT VALUE FOR @Sequence 的工作方式是不是与 C# 中的 Interlocked.Increment() 相同? [复制]

C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEv

《C#高级编程》读书笔记(十五):任务线程和同步之四 同步

Interlocked 是不是在所有线程中提供可见性?

当 Interlocked 类可用时,为啥在 .NET 中使用 SyncLocks 进行简单操作?