多核CPU上32位读取的原子性 [英] Atomicity of 32bit read on multicore CPU
问题描述
(注意:我已根据可能会帮助您的人们的意愿在此问题上添加标签,因此请不要大喊:))
(Note: I've added tags to this question based on where I feel will people will be who are likely to be able to help, so please don't shout:))
在我的VS 2017 64位项目中,我有一个32位长值m_lClosed
.当我想更新它时,我使用了Interlocked
系列功能之一.
In my VS 2017 64bit project, I have a 32bit long value m_lClosed
. When I want to update this, I use one of the Interlocked
family of functions.
考虑以下代码,在线程#1上执行
Consider this code, executing on thread #1
LONG lRet = InterlockedCompareExchange(&m_lClosed, 1, 0); // Set m_lClosed to 1 provided it's currently 0
现在考虑下面的代码,在线程#2上执行:
Now consider this code, executing on thread #2:
if (m_lClosed) // Do something
我知道在单个CPU上,这不会有问题,因为更新是原子的,读取也是原子的(请参阅
I understand that on a single CPU, this will not be a problem because the update is atomic and the read is atomic too (see MSDN), so thread pre-emption cannot leave the variable in a partially updated state. But on a multicore CPU, we really could have both these pieces of code executing in parallel if each thread is on a different CPU. In this example, I don't think that would be a problem, but it still feels wrong to be testing something that is in the process of possibly being updated.
此网页告诉我,多个CPU的原子性是通过LOCK
汇编指令实现的,阻止其他CPU访问该内存.这听起来像我所需要的,但是为上面的if测试生成的汇编语言仅仅是
This webpage tells me that atomicity on multiple CPUs is achieved via the LOCK
assembly instruction, preventing other CPUs from accessing that memory. That sounds like what I need, but the assembly language generated for the if test above is merely
cmp dword ptr [l],0
...看不到LOCK
指令.
我们应该如何在这种情况下确保读取的原子性?
修改18年4月24日
首先,感谢您对此问题的浓厚兴趣.我在下面显示实际代码;我故意将重点放在所有内容的原子性上保持简单,但是显然,如果我从第一分钟开始就展示了所有内容,那会更好.
Firstly thanks for all the interest this question has generated. I show below the actual code; I purposely kept it simple to focus on the atomicity of it all, but clearly it would have been better if I had showed it all from minute one.
第二,实际代码所在的项目是VS2005项目; 因此无法访问C ++ 11原子.这就是为什么我没有在问题中添加C ++ 11标记的原因.我正在将VS2017与临时"项目一起使用,以节省每次学习时进行更改时都不必构建大型VS2005的麻烦.另外,它是一个更好的IDE.
Secondly, the project in which the actual code lives is a VS2005 project; hence no access to C++11 atomics. That's why I didn't add the C++11 tag to the question. I am using VS2017 with a "scratch" project to save having to build the huge VS2005 one every time I make a change whilst I am learning. Plus, its a better IDE.
是的,因此实际的代码位于IOCP驱动的服务器中,而这整个原子性是关于处理封闭的套接字的:
Right, so the actual code lives in an IOCP driven server, and this whole atomicity is about handling a closed socket:
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()
等.请不要质疑为什么会这样,它只是:)
The caller will call SetClosed()
; if it returns true, it will then call ::closesocket()
etc. Please don't question why it is that way, it just is :)
考虑一个线程关闭套接字而另一个线程尝试发布WSARecv()
时会发生什么.您可能会认为WSARecv()
将会失败(毕竟套接字已关闭!);但是,如果使用与我们刚刚关闭的套接字相同的套接字句柄建立了新连接,该怎么办-然后我们将发布WSARecv()
,它将成功,但这对我的程序逻辑来说是致命的,因为我们现在将一个完全不同的连接与此CConnection对象相关联.因此,我有if (!m_lClosed)
测试.您可能会争辩说,我不应该在多个线程中处理相同的连接,但这不是这个问题的重点 :)
Consider what happens if one thread closes the socket whilst another tries to post a WSARecv()
. You might think that the WSARecv()
will fail (the socket is closed after all!); however what if a new connection is established with the same socket handle as that which we just closed - we would then be posting the WSARecv()
which will succeed, but this would be fatal for my program logic since we are now associating a completely different connection with this CConnection object. Hence, I have the if (!m_lClosed)
test. You could argue that I shouldn't be handling the same connection in multiple threads, but that is not the point of this question :)
这就是为什么在进行WSARecv()
调用之前需要测试m_lClosed
的原因.
That is why I need to test m_lClosed
before I make the WSARecv()
call.
现在,很明显,我只是将m_lClosed
设置为1,所以读/写操作撕裂并不是一个真正的问题,但是这是我关注的原则.如果将m_lClosed
设置为2147483647然后测试2147483647怎么办?在这种情况下,读/写操作将很麻烦.
Now, clearly, I am only setting m_lClosed
to a 1, so a torn read/write is not really a concern, but it is the principle I am concerned about. What if I set m_lClosed
to 2147483647 and then test for 2147483647? In this case, a torn read/write would be more problematic.
推荐答案
确定,因此事实证明这确实没有必要; 此答案详细说明了为什么我们不需要为简单的读/写操作而使用任何互锁的操作(但我们确实进行读取-修改-写入).
OK so as it turns out this really isn't necessary; this answer explains in detail why we don't need to use any interlocked operations for a simple read/write (but we do for a read-modify-write).
这篇关于多核CPU上32位读取的原子性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!