Monitor.Enter(object,ref bool)锁定获取的实现和顺序

Eya*_*rry 2 c# multithreading locking thread-safety race-condition

从Reference Source我们可以看到上述方法实现如下:

 public static void Enter(Object obj, ref bool lockTaken)
    {
        if (lockTaken)
            ThrowLockTakenException();

        ReliableEnter(obj, ref lockTaken);
        Contract.Assert(lockTaken);
    }
Run Code Online (Sandbox Code Playgroud)

我有一个问题是"if"声明 - 我理解它的重要性,没有必要解释它.我相信这句话也引入了一个非常微妙的问题.

请考虑以下情形:一个线程调用方法并评估if语句,此时发生上下文切换,另一个调用方法,成功获取锁定.

这是一个可行的场景,它表明锁获取的顺序不一定与方法调用的顺序相同.

虽然它在某些情况下可能没有什么作用,但在其他情况下它可能至关重要,所以我的问题是:

1)我是否遗漏了某些内容,或者Monitor类不适合在不可接受的情况下使用?

2)ReliableEnter是否可能遇到类似的情况?

3)是否存在同步机制,确保方法调用的顺序与锁获取的顺序相同?

Eri*_*ert 12

一个线程调用该方法并评估if语句,此时发生上下文切换,另一个调用方法,成功获取锁.

这完全有可能.

或者,可能没有上下文切换但仍然会发生这种情况.两个线程都通过"if"并进入ReliableEnter,两者都没有上下文切换,一个获胜.你能想到发生这种情况的情景吗?

这是一个可行的场景,它表明锁获取的顺序不一定与方法调用的顺序相同.

记住三位裁判的故事.

三位裁判正在吃午饭.第一个说"我看到他们就叫他们".第二个说"我称他们为他们".第三个人说:"在我打电话给他们之前,他们并不算什么."

第一位裁判认为观察是不完美的,人们可能不同意球场是球还是球.第二个人认为,有一个一致的物理世界,他们有准确的信息; 可以客观地确定球是球还是球.第三个人认为,无论世界是否一致和可知,棒球规则都说罢工是裁判所称的罢工.

你是第二个裁判.您认为在多线程世界中确实存在"方法调用发生的顺序"这样的事情,并且有一种方法可以了解它.但实际上这个世界更像是第一个裁判员:每个人都可以看到一个不同的世界并且不同意它.而锁更像是第三个裁判:锁是对无序世界强加秩序的东西.这些方法的命令是什么顺序?拿锁并找出答案.

放弃了在多线程世界中事物发生的一致顺序的神话.没有这样一致的顺序.每个处理器都可以在内存模型约束中尽可能多地重新排序代码.每个线程都可以看到不同的读写顺序.

每个线程都同意的是锁定的顺序,因此考虑调用方法的"顺序"是明智的.

那么,锁可以以与方法调用不同的顺序发生吗?第一位裁判说"没有人能确切知道".第二个说"是".第三个说"在锁定之前根本没有订单".

我同意第三位裁判员的意见.锁可以采用与调用方法不同的顺序吗?由于强加该命令的唯一方法是采取锁定,这不是一个明智的问题.

1)我是否遗漏了某些内容,或者Monitor类不适合在不可接受的情况下使用?

这是看错的方法.

如果我们继续相信存在调用发生顺序的虚构,并且你有一个程序依赖于完全神话般的顺序锁定它的正确性,那么该程序就有一个错误.锁定不是 "按照呼叫发生的顺序",故事结束,如果依赖于此,那么您将依赖于任何人所做的保证并且经常违反.

2)ReliableEnter是否可能遇到类似的情况?

我不明白这个问题.你的意思是,如果我们更换一次呼叫Enter与调用ReliableEnter,将我们仍然有同样的问题?是的,一点没错.

3)是否存在同步机制,确保方法调用的顺序与锁获取的顺序相同?

没有这样的顺序独立于锁,所以没有.

我有一个问题是"if"声明 - 我理解它的重要性,没有必要解释它.我相信这句话也引入了一个非常微妙的问题.

你的信念是错的.问题是由于在多线程世界中没有"方法调用发生的顺序"这样的事实."如果"与它没有任何关系.

此外,即使在我们可以从锁中提取订购信息的世界中,锁也不能保证是公平的.如果你有10个线程都在等待锁定,那么C#语言就没有要求"等待最长"或"先调用"的线程 - 无论这意味着什么 - 是访问监视器的线程下一个.操作系统具有广泛的自由度,可以根据需要调度线程,并且无限期地使线程处于饥饿状态.

现在,我上面所说的有很多半真半假.世界的实际状况非常混乱.如果你想知道真相那么你应该阅读C#规范中有关多线程程序中副作用排序的部分,它描述了保证观察到特定顺序的事件:锁,线程的创建,某些类型读写,异常和构造函数调用.在某些情况下,订购保证非常薄弱.

正如我一直这样做的那样,我建议你阅读我关于如何在没有锁的世界中进行重新排序的文章.看看你是否能解决我在第二部分中提出的难题.

http://blog.coverity.com/2014/03/12/can-skip-lock-reading-integer/ http://blog.coverity.com/2014/03/26/reordering-optimizations/