【问题标题】:Implementing synchronization algorithm in C#在 C# 中实现同步算法
【发布时间】:2010-01-01 20:08:35
【问题描述】:

我尝试在 C# 中实现同步算法,但没有成功。

为什么下面的代码不是线程安全的?

using System;
using System.Threading;

namespace SoftwareLockTest
{
    class Program
    {
        private static volatile bool _isLocked1 = false;
        private static volatile bool _isLocked2 = false;
        private static volatile int _count = 0;

        static void Main(string[] args)
        {
            Thread thread2 = new Thread(Thread2Work);
            thread2.Start();

            Thread1Work();
        }

        public static void Thread1Work()
        {
            while (true)
            {
                _isLocked1 = true;

                while (_isLocked2)
                {
                    _isLocked1 = false;
                    while (_isLocked2) ;
                    _isLocked1 = true;
                }

                CriticalSection();
                _isLocked1 = false;
            }
        }

        public static void Thread2Work()
        {
            while (true)
            {
                _isLocked2 = true;

                while (_isLocked1)
                {
                    _isLocked2 = false;
                    while (_isLocked1) ;
                    _isLocked2 = true;
                }

                CriticalSection();
                _isLocked2 = false;
            }
        }

        private static void CriticalSection()
        {
            if (_count != 0)
            {
                Console.WriteLine("NOT THREAD SAFE 1");
                _count = 0;
            }

            _count++;

            if (_count != 1)
            {
                Console.WriteLine("NOT THREAD SAFE 2");
            }

            _count--;

            if (_count != 0)
            {
                Console.WriteLine("NOT THREAD SAFE 3");
            }
        }
    }
}

【问题讨论】:

  • 这可能是一个不错的实验,但对于实际工作,您应该使用 Monitor 和 lock。
  • 这不是作业,只是实验

标签: c# .net multithreading synchronization thread-safety


【解决方案1】:

问题是写后读可能会被重新排序(即使是“易失性”)。您需要调用 Thread.MemoryBarrier();在四个“while (_isLockedX)”循环之前。

请阅读http://www.albahari.com/threading/part4.aspx 了解内存屏障和易失性的说明。

对于任何实际项目,请优先使用现有的锁实现,而不是尝试自己制作。

【讨论】:

  • 谢谢你,Thread.MemoryBarrier();解决了这个问题。我做了一些基准测试,发现它的运行速度比内置锁快 2 倍。
  • 哪个内置锁? System.Threading.SpinLock 将与您的实现最相似。 lock(){} 有额外的开销,因为它在等待释放锁时使线程进入睡眠状态。此外,您的实现可能会导致性能问题 - 旋转循环不断访问内存总线,从而减慢其他处理器的速度。在循环中调用 Thread.SpinWait() 让处理器在进行下一次内存访问之前稍等片刻。在 .NET 4.0 中,System.Threading.SpinWait 将帮助您旋转(指数退避,如果短时间旋转不够,则使线程进入睡眠状态)。
  • 此外,Thread.SpinWait 会告诉处理器它正在旋转(PAUSE asm 指令,请参阅siyobik.info/index.php?module=x86&id=232),从而提高超线程处理器的性能。
【解决方案2】:

您正在尝试实现Dekker's algorithm。不幸的是,他生活在更简单的时代,那时硬件设计师和软件工程师还在互相交谈。 PC 业务供应商之间的激烈竞争,强调速度和核心,对 Dekker 先生的独创性造成了毁灭性的打击。有点高兴,我一定已经复习了几十次这个算法,而且从来没有头疼过。

嗯,这有点像元。查看该 Wikipedia 文章中的“注释”,了解该算法为何不再起作用。你有很多可行的替代方案。关键是,您找到的任何超过 5 年的关于并发的文献都不再适用。

【讨论】:

    【解决方案3】:

    嗯,目前还不完全清楚你想用锁标志做什么,理解代码对于线程是关键,但在没有lock/Monitor的情况下,我希望看到很多 Interlocked 用于增量(.Increment)/减量(.Decrement)/测试(.CompareExchange)。仅仅因为它是volatile 并不意味着在执行++/-- 时两个线程不会被绊倒。

    但坦率地说,我只会使用lock,除非你有充分的理由不这样做。您希望保持简单 - “显然没有错误”,而不是“没有明显错误”。

    【讨论】:

    • 他的“CriticalSection”受到锁的保护——线程方法中的“while (_isLockedX)”循环实现了自旋锁。问题是他的自旋锁实现有一个错误(缺少内存屏障)。
    • 还不错;我赞成您的(更有见地的)答案,但我将把它留在这里,因为我认为它仍然提出了一些可能对“长尾”有用的有效观点。
    【解决方案4】:

    我仍在努力解决这个问题,显然通常您确实应该使用锁...但我怀疑问题是 volatile 可能并不完全符合您的想法它应该。

    我希望您认为它的意思是“当我写入这个变量时,立即使那个写入可见;当我从这个变量中读取时,读取一个绝对最新的值”。 It doesn't mean quite that(尽管 MSDN 这么说,而且确实是我自己的线程教程;当我对它有更好的处理时,我需要更新它)。

    我会看看我能不能弄清楚到底发生了什么,但它看起来确实很奇怪。我想我理解您的代码正在尝试做什么,并且在做出关于波动性的“简单”假设时我还没有设法打破它......(我已经重现了这个问题,这总是有帮助的)

    【讨论】:

      【解决方案5】:

      有趣的是,几个小时前,codeguru 上也出现了同样的查询……这是我的回复,改编自那里,但最重要的是你需要一个内存栅栏才能让这段代码正常工作。

      您的代码的问题在于它依赖于您的系统来保持顺序一致。 x86 系统不是 SC。它们就是所谓的处理器一致。

      它非常微妙,所以让我们稍微简化一下代码:

      thread1:
      
      WRITE _isLocked1, 1
      READ _isLocked2
      (skip the while loop)
      critical section
      WRITE _isLocked1, 0
      
      thread2:
      
      WRITE _isLocked2, 1
      READ _isLocked1
      (skip the while loop)
      critical section
      WRITE _isLocked2, 0
      

      处理器一致性只是说线程 1 按照它们执行的顺序观察线程 2 完成的写入(所以先写 1 然后写 0)。相反,线程 2 观察线程 1 的写入。令您痛苦的是处理器一致性几乎没有说明线程 1 的写入如何与线程 2 的写入交错,只是保持了相关操作的因果关系(这对您的示例并不重要)。

      AMD 文档第 2 卷,第 7.2 节有一组很好的示例,可以帮助您了解它并参考 Dekker 的算法以及为什么它需要 x86 系统上的内存栅栏。

      【讨论】:

      • 他写的不是汇编程序,而是C#。在这方面,.NET 内存模型恰好与 x86 内存模型相匹配。对于其他处理器(例如 IA-64),.NET 运行时将根据需要插入额外的屏障等,以在这些处理器上模拟 .NET 内存模型。您的 ASM 代码在具有单处理器(单核)的 x86 系统上是正确的 - 但他的 C# 代码在 .NET 内存模型中仍然不正确。编译器也可以重新排序指令。
      【解决方案6】:

      你为什么不直接使用lock

      lock (_anyInstantiatedObject)
      {
          CriticalSection();
      }
      

      这样您就可以依靠操作系统来确保没有其他线程同时进入临界区。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-17
        • 1970-01-01
        • 1970-01-01
        • 2015-03-29
        • 2012-11-25
        • 1970-01-01
        相关资源
        最近更新 更多