在一篇关于A scalable reader/writer scheme with optimistic retry的文章中,有一个代码示例:
using System;
using System.Threading;
public class OptimisticSynchronizer
{
private volatile int m_version1;
private volatile int m_version2;
public void BeforeWrite() {
++m_version1;
}
public void AfterWrite() {
++m_version2;
}
public ReadMark GetReadMark() {
return new ReadMark(this, m_version2);
}
public struct ReadMark
{
private OptimisticSynchronizer m_sync;
private int m_version;
internal ReadMark(OptimisticSynchronizer sync, int version) {
m_sync = sync;
m_version = version;
}
public bool IsValid {
get { return m_sync.m_version1 == m_version; }
}
}
public void DoWrite(Action writer) {
BeforeWrite();
try {
writer(); // this is inlined, method call just for example
} finally {
AfterWrite();
}
}
public T DoRead<T>(Func<T> reader) {
T value = default(T);
SpinWait sw = new SpinWait();
while (true) {
ReadMark mark = GetReadMark();
value = reader();
if (mark.IsValid) {
break;
}
sw.SpinOnce();
}
return value;
}
}
如果我m_version1
和m_version2
不易变,请使用以下代码:
public void DoWrite(Action writer) {
Thread.MemoryBarrier(); // always there, acquiring write lock with Interlocked method
Volatile.Write(ref m_version1, m_version1 + 1); // NB we are inside a writer lock, atomic increment is not needed
try {
writer();
} finally {
// is a barrier needed here to avoid the increment reordered with writer instructions?
// Volatile.Write(ref m_version2, m_version2 + 1); // is this needed instead of the next line?
m_version2 = m_version2 + 1; // NB we are inside a writer lock, atomic increment is not needed
Thread.MemoryBarrier(); // always there, releasing write lock with Interlocked method
}
}
行m_version2 = m_version2 + 1
的说明是否可以从finally
重新排序到try
块?作者在m_version2
递增之前完成是很重要的。
逻辑finally
在try
后执行,但list of implicit memory barriers中未提及finally
块。 如果来自finally
的指令可以在来自try
的指令之前移动,那将会非常令人困惑,但是在指令级别的CPU优化对我来说仍然是一个黑魔法。
我可以将Thread.MemoryBarrier();
放在第m_version2 = m_version2 + 1
行之前(或使用Volatile.Write
),但问题是这是否真的需要?
示例中显示的MemoryBarrier
是隐式的,由编写器锁的Interlocked
方法生成,因此它们始终存在。危险在于读者可以在作者完成之前看到m_version2
递增。
答案 0 :(得分:1)
我没有找到任何会限制它的规格,所以我用ARM CPU设备检查了它(使用Xamarin ,必须在Core CLR上检查它...)<登记/> 一个线程正在执行此代码:
try
{
person = new Person();
}
finally
{
isFinallyExecuted = true;
}
第二个帖子正在等待isFinallyExecuted
使用此代码true
:
while (!Volatile.Read(ref isFinallyExecuted))
;
然后第二个线程正在执行以下代码:
if (!person.IsInitialized())
{
failCount++;
Log.Error("m08pvv", $"Reordered from finally: {failCount}, ok: {okCount}");
}
else
{
okCount++;
}
IsInitialized
方法检查所有字段是否已正确设置,因此它会为部分构造的对象返回false
。
这就是我在日志中得到的:
12-25 17:00:55.294:E / m08pvv(11592):从最后重新排序:48,好的: 682245
12-25 17:00:56.750:E / m08pvv(11592):重新排序自 最后:49,确定:686534
12-25 17:00:56.830:E / m08pvv(11592): 从最后重新排序:50,确定:686821
12-25 17:00:57.310: E / m08pvv(11592):从最后重新排序:51,确定:688002
12-25 17:01:12.191:E / m08pvv(11592):从最后重新排序:52,好的: 733724
12-25 17:01:12.708:E / m08pvv(11592):重新排序自 最后:53,确定:735338
12-25 17:01:13.722:E / m08pvv(11592): 从最后重新排序:54,确定:738839
12-25 17:01:25.240: E / m08pvv(11592):最后重新订购:55,好的:775645
这意味着,对于775645次成功运行该代码,55次我得到isFinallyExecuted
等于true
和部分构造的对象。这是可能的,因为我没有使用Volatile.Write
在new Person()
上存储volatile
或person
个关键字。
所以,如果你有一些数据竞赛,你将面对它们。