在C#get和set线程中使用联锁是否安全?
是否可以通过在属性访问器中使用在C#get和set线程中使用联锁是否安全?,c#,multithreading,thread-safety,interlocked,C#,Multithreading,Thread Safety,Interlocked,是否可以通过在属性访问器中使用Interlocked来获取线程安全属性 例如: public class A() { private static long count; public static long Count { get { return Interlocked.Read(ref count); } set { Interlocked
Interlocked
来获取线程安全属性
例如:
public class A()
{
private static long count;
public static long Count
{
get
{
return Interlocked.Read(ref count);
}
set
{
Interlocked.Exchange(ref count, value);
}
}
}
当运行上述示例时,
get
和set
访问器的执行行为是可线性化的。如果不使用联锁
,get
和set
访问器的执行行为介于弱一致性和顺序一致性之间(即,仅保证表现出弱一致性)
当作为64位进程运行时,同样可以通过标记字段
volatile
并使用简单的返回语句和赋值运算符来实现。但是,当作为32位进程运行时,volatile
64位字段上的操作不能保证是原子的,因此需要使用联锁的
,以确保原子性。它不是线程安全的。请尝试以下代码:
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,而是稍微低一点,证明它不是线程安全的。不确定为什么会发生这种情况“线程安全”是整个程序的属性,而不仅仅是其中的一个片段,而且在您明确定义“线程安全”的含义之前,它也不是一个特别有意义的术语,这意味着在整个程序中会/不会满足哪些条件。话虽如此,我想说的是,一般来说,看到这样的属性是一种代码味道。这对我来说是一个信号,有人认为某些东西会正常工作,而事实上它不会正常工作,尽管我们需要查看属性的每个用法才能确定。关于示例代码的“线程安全性”,我们唯一能说的是,Count的值实际上是由属性集分配给它的值(如果您用简单的分配和返回替换联锁,则不能说什么)但是,正如Servy所说,如果你不确切地定义你所说的“线程安全”属性,这并不意味着什么。@MaMazav你甚至不能这么说,真的。
可以互锁。Read
返回一个值,将其存储在临时文件中,调用setter,更改值,然后重新调用getter反过来,你甚至不能说getter不返回一个过时的值。增加的内存障碍有助于它不那么过时,并防止来自同一线程的get和set的重新排序,但是当涉及多个线程时,由于将值存储在临时(隐式完成)中,你仍然可以做一些非常有趣的事情在它返回之前。原子性和线程安全是两个截然不同的概念,从这段代码中得到的原子性保证是非常弱的。您所能做的就是承诺,当您的程序以32位模式运行时,您永远不会读取部分更新的值。@Servy我唯一的声明是,他编写的代码承诺返回值必须是过去分配给它(而不是汉帕桑所说的部分分配)。我不明白如何将值存储在临时文件中会破坏它。那么……这是肯定还是否定?@Henkholtman在这种情况下,这个问题没有意义,因此不能有简单的肯定或否定的答案。从与相关属性交互的外部代码的角度来看,我的答案是准确的。@280Z28当问题不是answ时由于缺乏信息,您应该投票关闭它,而不是给出一个不完整且毫无帮助的答案。@280Z28此时,您已经给出了一些合理的声明,说明了该代码所做的保证。问题是,我们不知道这些保证是否真的足以满足OP的目的,因为没有id再加上大多数人实际上并不理解“弱一致性”和“顺序一致性”等概念正如你所提到的,在我看来,读者根据这个答案判断他们的代码是否有效的概率似乎很低。++postfix将捕获i的值并使其增加1。在i的值增加之前(读取之后),多个线程可以安全地读取i的值。之后,这些不同的线程将“安全地”将相同的值写在i上。