【问题标题】:Property with Volatile or Lock具有易失性或锁定的属性
【发布时间】:2014-02-12 09:36:05
【问题描述】:

我有一个带有支持字段的属性,我想使线程安全(获取和设置)。 get和set方法除了设置和返回没有任何逻辑。

我认为有两种方法可以封装属性 self 中的逻辑(易失性和锁定)。 我对两者的理解是正确的还是我犯了任何错误?

以下是我的例子:

    public class ThreadSafeClass
    {
        // 1. Volatile Example:

        private volatile int _processState_1;
        public int ProcessState_1
        {
            get { return _processState_1; }
            set { _processState_1 = value; }
        }

        // 2. Locking Example:

        private readonly object _processState_2Lock = new object();
        private int _processState_2;
        public int ProcessState_2
        {
            get
            {
                lock (_processState_2Lock)
                {
                    return _processState_2;
                }
            }
            set
            {
                lock (_processState_2Lock)
                {
                    _processState_2 = value;
                }
            }
        }
    }

【问题讨论】:

  • 您的理解似乎肯定是错误的(或至少不完整),但很难提供具体的回应,因为那只是一堆代码。您根本没有提及您的理解到底是什么。另外,请注意“线程安全”没有单一定义,因此您自己的定义也不清楚。

标签: c# .net multithreading locking volatile


【解决方案1】:

有关更多信息,请参阅great site by J. Albahari

同步构造可以分为四类:

简单的拦截方法:

这些等待另一个线程完成或经过一段时间。 SleepJoinTask.Wait 是简单的阻塞方法。

锁定结构:

这些限制了一次可以执行某些活动或执行一段代码的线程数。排他锁结构是最常见的——它们一次只允许一个线程进入,并允许竞争线程访问公共数据而不会相互干扰。标准的排他锁定结构是lock (Monitor.Enter/Monitor.Exit)、MutexSpinLock。非独占的locking 构造是SemaphoreSemaphoreSlimreader/writer 锁。

信号结构:

这些允许线程暂停,直到收到另一个通知,避免了低效轮询的需要。有两种常用的信号设备:事件等待句柄和 Monitor 的 Wait/Pulse 方法。 Framework 4.0 引入了CountdownEventBarrier 类。

非阻塞同步结构:

它们通过调用处理器原语来保护对公共字段的访问。 CLR 和 C# 提供以下非阻塞构造:Thread.MemoryBarrierThread.VolatileReadThread.VolatileWritevolatile 关键字和 Interlocked 类。


volatile 关键字:

volatile 关键字指示编译器在每次读取该字段时生成一个获取栅栏,并在每次写入该字段时生成一个释放栅栏。获取栅栏防止其他读/写在栅栏之前移动;释放栅栏防止其他读/写在栅栏之后移动。这些“半栅栏”比全栅栏更快,因为它们为运行时和硬件提供了更大的优化空间。

事实上,英特尔的 X86 和 X64 处理器总是对读取应用获取栅栏,对写入应用释放栅栏——无论你是否使用 volatile 关键字——所以如果你使用它们,这个关键字对硬件没有影响处理器。但是,volatile 确实对编译器和 CLR 以及 64 位 AMD 和(在更大程度上)安腾处理器执行的优化有影响。这意味着您不能因为您的客户端运行特定类型的 CPU 而放松。

对字段应用volatile的效果可以总结如下:

First instruction   Second instruction  Can they be swapped?
Read                Read                No
Read                Write               No
Write               Write               No (The CLR ensures that write-write operations are never swapped, even without the volatile keyword)
Write               Read                Yes!

请注意,应用 volatile 不会阻止交换写入后读取,这可能会产生脑筋急转弯。 Joe Duffy 用下面的例子很好地说明了这个问题:如果 Test1Test2 在不同的线程上同时运行,那么 a 和 b 都可能以 0 的值结束(尽管在 @987654346 上都使用了 volatile @和y):

class IfYouThinkYouUnderstandVolatile
{
  volatile int x, y;

  void Test1()        // Executed on one thread
  {
    x = 1;            // Volatile write (release-fence)
    int a = y;        // Volatile read (acquire-fence)
    ...
  }

  void Test2()        // Executed on another thread
  {
    y = 1;            // Volatile write (release-fence)
    int b = x;        // Volatile read (acquire-fence)
    ...
  }
}

MSDN 文档指出,使用 volatile 关键字可确保字段中始终存在最新的值。这是不正确的,因为正如我们所见,写入后读取可以重新排序。

这为避免 volatile 提供了一个强有力的案例:即使您了解此示例中的微妙之处,其他开发您的代码的开发人员也会理解它吗? Test1Test2(或锁)中的两个分配之间的完整栅栏解决了这个问题。

传递引用参数或捕获的局部变量不支持volatile 关键字:在这些情况下,您必须使用VolatileReadVolatileWrite 方法。

【讨论】:

    猜你喜欢
    • 2018-02-28
    • 2010-09-14
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-23
    • 1970-01-01
    相关资源
    最近更新 更多