(注意:我根据我认为可能提供帮助的人在哪里添加了标签到这个问题,所以请不要喊:))
在我的 VS 2017 64 位项目中,我有一个 32 位长值m_lClosed
。当我想更新它时,我使用其中之一Interlocked
函数族。
考虑这段代码,在线程 #1 上执行
LONG lRet = InterlockedCompareExchange(&m_lClosed, 1, 0); // Set m_lClosed to 1 provided it's currently 0
现在考虑在线程 #2 上执行的这段代码:
if (m_lClosed) // Do something
我知道在单个 CPU 上,这不会成为问题,因为更新是原子的,读取也是原子的(请参阅MSDN),因此线程抢占不能使变量处于部分更新的状态。但在多核 CPU 上,如果每个线程位于不同的 CPU 上,我们确实可以并行执行这两段代码。在这个例子中,我认为这不会是一个问题,但测试可能正在更新的东西仍然感觉不对。
这个网页告诉我多 CPU 上的原子性是通过以下方式实现的LOCK
汇编指令,防止其他 CPU 访问该内存。这听起来像是我所需要的,但是为上面的 if 测试生成的汇编语言仅仅是
cmp dword ptr [l],0
... no LOCK
指令就在眼前。
在这样的情况下,我们应该如何确保读取的原子性?
编辑 24/4/18
首先感谢大家对这个问题的关注。我在下面展示了实际的代码;我故意保持简单,以关注这一切的原子性,但显然,如果我从第一分钟就展示出这一切,那就更好了。
其次,实际代码所在的项目是VS2005项目;因此无法访问 C++11 原子。这就是为什么我没有在问题中添加 C++11 标签。我将 VS2017 与“临时”项目一起使用,这样每次我在学习时进行更改时都不必构建巨大的 VS2005 项目。另外,它是一个更好的 IDE。
是的,所以实际的代码位于 IOCP 驱动的服务器中,整个原子性是关于处理关闭的套接字:
class CConnection
{
//...
DWORD PostWSARecv()
{
if (!m_lClosed)
return ::WSARecv(...);
else
return WSAESHUTDOWN;
}
bool SetClosed()
{
LONG lRet = InterlockedCompareExchange(&m_lClosed, 1, 0); // Set m_lClosed to 1 provided it's currently 0
// If the swap was carried out, the return value is the old value of m_lClosed, which should be 0.
return lRet == 0;
}
SOCKET m_sock;
LONG m_lClosed;
};
呼叫者将呼叫SetClosed()
;如果返回 true,则会调用::closesocket()
等等。请不要质疑为什么会这样,它就是这样:)
考虑一下如果一个线程关闭套接字而另一个线程尝试发布套接字,会发生什么情况WSARecv()
。你可能会认为WSARecv()
将失败(套接字毕竟已关闭!);但是,如果与建立新连接怎么办?与我们刚刚关闭的套接字句柄相同- 然后我们将发布WSARecv()
这会成功,但这对我的程序逻辑来说是致命的,因为我们现在将一个完全不同的连接与这个 CConnection 对象关联起来。因此,我有if (!m_lClosed)
测试。你可能会说我不应该在多个线程中处理相同的连接,但这不是这个问题的重点 :)
这就是为什么我需要测试m_lClosed
在我做之前WSARecv()
call.
现在,显然,我只是设置m_lClosed
到 1,所以读/写撕裂并不是真正的问题,但是这是我关心的原则。如果我设置怎么办m_lClosed
到 2147483647,然后测试 2147483647?在这种情况下,读/写撕裂会产生更大的问题。