我正在创建两个线程,并传递给它们一个函数,它执行下面的代码显示,10,000,000次。
大多数情况下,"5“被打印到控制台上。有时是"3“或"4”。很清楚为什么会发生这种事。
然而,它也打印"6“。这怎麽可能?
class Program
{
private static int _state = 3;
static void Main(string[] args)
{
Thread firstThread = new Thread(Tr);
Thread secondThread = new Thread(Tr);
firstThread.Start();
secondThread.Start();
firstThread.Join();
secondThread.Join();
Console.ReadLine();
}
private static void Tr()
{
for (int i = 0; i < 10000000; i++)
{
if (_state == 3)
{
_state++;
if (_state != 4)
{
Console.Write(_state);
}
_state = 3;
}
}
}
}这是输出:

发布于 2015-10-25 22:46:30
我想我已经弄清楚了导致这一问题的一系列事件:
线程1进入if (_state == 3)
上下文切换
线程2进入if (_state == 3)
线程2增量状态(state = 4)
上下文切换
线程1将_state读入4
上下文切换
线程2集_state = 3
线程2进入if (_state == 3)
上下文切换
线程1执行_state = 4 + 1
上下文切换
线程2将_state读入5
线程2执行_state = 5 + 1;
发布于 2015-10-25 23:25:42
这是一个典型的种族条件。编辑:事实上,有多个种族条件。
如果_state为3,两个线程都可以通过if语句并发地在单个核中进行上下文切换,或者在多个核中同时并行。
这是因为++操作符首先读取_state,然后递增它。在第一次if声明之后,有可能会有足够的时间来读5甚至6。
编辑:如果您将这个示例概括为N个线程,您可能会观察到一个高达3+ N+1的数字。
当线程开始运行时,或者当一个线程刚刚将_state设置为3时,这是正确的。
要避免这种情况,可以在if语句周围使用锁,或者使用Interlocked访问_state,例如if (System.Threading.Interlocked.CompareAndExchange(ref _state, 3, 4) == 3)和System.Threading.Interlocked.Exchange(ref _state, 3)。
如果您想保持争用条件,您应该使用volatile,否则您将使每个线程在本地看到_state而不需要其他线程的更新。
另外,您可以使用System.Threading.Volatile.Read和System.Threading.Volatile.Write,以防您将实现切换为以_state作为变量,而将Tr作为捕获该变量的闭包,因为局部变量不能被声明为volatile (也不可能)。在这种情况下,即使初始化也必须使用易失性写入。
编辑:如果我们通过扩展每次读取来稍微修改代码,那么竞争条件可能会更加明显:
// Without some sort of memory barrier (volatile, lock, Interlocked.*),
// a thread is allowed to see _state as if other threads hadn't touched it
private static volatile int _state = 3;
// ...
for (int i = 0; i < 10000000; i++)
{
int currentState;
currentState = _state;
if (currentState == 3)
{
// RACE CONDITION: re-read the variable
currentState = _state;
currentState = currentState + 1:
// RACE CONDITION: non-atomic read-modify-write
_state = currentState;
currentState = _state;
if (currentState != 4)
{
// RACE CONDITION: re-read the variable
currentState = _state;
Console.Write(currentState);
}
_state = 3;
}
}我在_state可能不同于以前变量read语句假设的地方添加了注释。
这是一个很长的图表,它甚至可以连续打印6次,每个线程一次,就像op发布的图像一样。请记住,线程可能不会同步运行,通常是由于抢占式上下文切换、缓存暂停或核心速度差异(由于省电或临时涡轮速度):

这个类似于最初的,但它使用Volatile类,其中state现在是一个由闭包捕获的变量。易失性访问的数量和顺序变得明显:
static void Main(string[] args)
{
int state = 3;
ThreadStart tr = () =>
{
for (int i = 0; i < 10000000; i++)
{
if (Volatile.Read(ref state) == 3)
{
Volatile.Write(ref state, Volatile.Read(state) + 1);
if (Volatile.Read(ref state) != 4)
{
Console.Write(Volatile.Read(ref state));
}
Volatile.Write(ref state, 3);
}
}
};
Thread firstThread = new Thread(tr);
Thread secondThread = new Thread(tr);
firstThread.Start();
secondThread.Start();
firstThread.Join();
secondThread.Join();
Console.ReadLine();
}一些线程安全的方法:
private static object _lockObject;
// ...
// Do not allow concurrency, blocking
for (int i = 0; i < 10000000; i++)
{
lock (_lockObject)
{
// original code
}
}
// Do not allow concurrency, non-blocking
for (int i = 0; i < 10000000; i++)
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_lockObject, ref lockTaken);
if (lockTaken)
{
// original code
}
}
finally
{
if (lockTaken) Monitor.Exit(_lockObject);
}
}
// Do not allow concurrency, non-blocking
for (int i = 0; i < 10000000; i++)
{
// Only one thread at a time will succeed in exchanging the value
try
{
int previousState = Interlocked.CompareExchange(ref _state, 4, 3);
if (previousState == 3)
{
// Allow race condition on purpose (for no reason)
int currentState = Interlocked.CompareExchange(ref _state, 0, 0);
if (currentState != 4)
{
// This branch is never taken
Console.Write(currentState);
}
}
}
finally
{
Interlocked.CompareExchange(ref _state, 3, 4);
}
}
// Allow concurrency
for (int i = 0; i < 10000000; i++)
{
// All threads increment the value
int currentState = Interlocked.Increment(ref _state);
if (currentState == 4)
{
// But still, only one thread at a time enters this branch
// Allow race condition on purpose (it may actually happen here)
currentState = Interlocked.CompareExchange(ref _state, 0, 0);
if (currentState != 4)
{
// This branch might be taken with a maximum value of 3 + N
Console.Write(currentState);
}
}
Interlocked.Decrement(ref _state);
}这个值有点不同,它使用增量后的_state的最后一个已知值来执行以下操作:
// Allow concurrency
for (int i = 0; i < 10000000; i++)
{
// All threads increment the value
int currentState = Interlocked.Increment(ref _state);
if (currentState != 4)
{
// Only the thread that incremented 3 will not take the branch
// This can happen indefinitely after the first increment for N > 1
// This branch might be taken with a maximum value of 3 + N
Console.Write(currentState);
}
Interlocked.Decrement(ref _state);
}注意,Interlocked.Increment/Interlocked.Decrement示例与lock/Monitor和Interlocked.CompareExchange示例不同,不安全,因为没有可靠的方法可以知道增量是否成功。
一种常见的方法是增量,然后在try/finally中减少finally块中的值。然而,)
异步异常可以抛出在意想不到的位置,可能是每个机器指令:ThreadAbortException、StackOverflowException和OutOfMemoryException。
另一种方法是将currentState初始化为低于3的内容,并在finally块中有条件地减少。但是,在Interlocked.Increment返回和分配给结果的currentState之间,可能会出现异步异常,因此即使Interlocked.Increment成功,currentState仍然可以具有初始值。
https://stackoverflow.com/questions/33335838
复制相似问题