【问题标题】:Why does Peterson's lock fail in this test?为什么彼得森的锁在这个测试中失败了?
【发布时间】:2016-09-05 21:16:17
【问题描述】:

我正在试验不需要原子指令的锁。彼得森的算法似乎是最简单的起点。但是,如果迭代次数足够多,就会出现问题。

代码:

public class Program
{
    private static volatile int _i = 0;
    public static void Main(string[] args)
    {
        for (int i = 0; i < 1000; i++)
        {
            RunTest();
        }
        Console.Read();
    }

    private static void RunTest()
    {
        _i = 0;
        var lockType = new PetersonLock();
        var t1 = new Thread(() => Inc(0, lockType));
        var t2 = new Thread(() => Inc(1, lockType));

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();
        Console.WriteLine(_i);
    }

    private static void Inc(int pid, ILock lockType)
    {
        try
        {
            for (int i = 0; i < 1000000; i++)
            {
                lockType.Request(pid);
                _i++;
                lockType.Release(pid);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

public interface ILock
{
    void Request(int pid);
    void Release(int pid);
}

public class NoLock : ILock
{
    public void Request(int pid) { }
    public void Release(int pid) { }
}

public class StandardLock : ILock
{
    private object _sync = new object();

    public void Request(int pid)
    {
        Monitor.Enter(_sync);
    }

    public void Release(int pid)
    {
        Monitor.Exit(_sync);
    }
}

public class PetersonLock : ILock
{
    private volatile bool[] _wantsCs = new bool[2];
    private volatile int _turn;

    public void Request(int pid)
    {
        int j = pid == 1 ? 0 : 1;
        _wantsCs[pid] = true;
        _turn = j;
        while (_wantsCs[j] && _turn == j)
        {
            Thread.Sleep(0);
        }
    }

    public void Release(int pid)
    {
        _wantsCs[pid] = false;
    }
}

当我运行它时,我始终得到

【问题讨论】:

  • 会不会是您使用了private int _turn; 而不是private volitile int _turn;,并且您遇到了cpu 缓存问题?
  • @ScottChamberlain 刚试过,好像没什么影响
  • 添加一个异常处理程序(try/catch)以查看您是否收到任何异常。程序可能只是由于异常而终止。
  • 这里的问题是_wantsCs[pid] = true;_turn = j;被重新排序了。尝试在两者之间添加Thread.MemoryBarrier();,您应该会得到正确的结果。
  • @jdweng 他正在加入线程,这意味着他将等待两个线程完全执行完。他还传递了两个不同的pid 值。

标签: c# multithreading locking


【解决方案1】:

这里的问题是这两条语句:

_wantsCs[pid] = true;
_turn = j;

.NET 和 C# 的内存模型允许对这两个写入重新排序。

要强制它们不被重新排序,请在它们之间添加一个内存屏障:

_wantsCs[pid] = true;
Thread.MemoryBarrier();
_turn = j;

你每次都会得到预期的输出。

请注意,这个问题在注释部分的Wikipedia page for Peterson's Algorithm 中进行了描述(此处缩短,请阅读文章以获取完整注释):

备注
...
大多数现代 CPU 重新排序内存访问以提高执行效率(请参阅内存排序以了解允许的重新排序类型)。这样的处理器总是提供一些方法来强制在内存访问流中进行排序,通常是通过内存屏障指令。 在重新排序内存访问的处理器上实施 Peterson 和相关算法通常需要使用此类操作才能正常工作,以防止顺序操作以不正确的顺序发生。

(我的重点)

【讨论】:

  • 哇!我已经读过这可能,但我从未真正看到它发生过。您是否必须查看反汇编才能弄清楚这一点,还是您在源代码中识别出一些常见模式?
  • 我以前见过它,所以对我来说很容易发现它是一个可能的问题,我不知道这是一个实际问题,直到我简单地测试了它。基本上,写入两个变量可以重新排序,以便其他线程可以观察到写入以相反的顺序发生。
猜你喜欢
  • 2012-07-20
  • 2021-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多