这段代码是线程安全的吗?
这是我当前维护的一些代码的简化版本:
int SomeFunc()
{
const long lIndex = m_lCurrentIndex;
int nSum = 0;
nSum += m_someArray[lIndex];
nSum += m_someArray[lIndex];
return nSum;
}
lCurrentIndex 由另一个线程定期更新。 问题是; 制作 m_CurrentIndex 的本地副本会确保对 m_someArray 的两次访问使用相同的索引吗?
请注意,这是一个简化的示例; 我正在考虑制作本地副本的概念,而不是此处显示的确切代码段。 我知道编译器会将值放入寄存器中,但这仍然是本地副本,而不是从 lCurrentIndex 读取两次。
谢谢!
编辑:初始分配是安全的,在我们的设置中都保证都是 32 位。 Edit2:并且它们在 32 位边界上正确对齐(忘记了那个)
This is a simplified version of some code I'm currently maintaining:
int SomeFunc()
{
const long lIndex = m_lCurrentIndex;
int nSum = 0;
nSum += m_someArray[lIndex];
nSum += m_someArray[lIndex];
return nSum;
}
lCurrentIndex is updated periodically by another thread. The question is; will making a local copy of m_CurrentIndex make sure both accesses to m_someArray uses the same index?
Please note that this is a simplified example; I'm thinking about the concept of making a local copy, not the exact piece of code shown here. I know the compiler will put the value in a register, but that is still a local copy as opposed to reading from lCurrentIndex twice.
Thanks!
Edit: The initial assignment is safe, both are guaranteed to be 32 bit in our setup.
Edit2: And they are correctly aligned on a 32bit boundary (forgot about that one)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
不,读取共享变量的本地初始化不一定是原子的。 (例如,考虑 8 位平台上需要什么代码)通常,编写线程安全代码的唯一方法是使用编译器和/或操作系统指定的原子操作,或者使用操作系统锁定功能。
No, the initialisation of the local, which reads a shared variable, is not necessarily atomic. (consider what code would be needed on an 8-bit platform, for example) In general, the only way to write thread safe code is to use atomic operations specified by your compiler and/or OS, or to use OS locking features.
在
SomeFunc
的同一执行中,是的,当然。 局部整型变量 (lIndex
) 不会在函数中间神奇地改变其值。当然,以下情况也是正确的:
m_someArray[lIndex]
的实际值(与lIndex
相对)可能会发生变化; m_someArray
本身可能会改变; 以及Neil 所说的关于 lIndex 初始值有效性的内容。In the same execution of
SomeFunc
, yes, of course. A local integer variable (lIndex
) will not magically change its value in the middle of the function.Of course, the following are also true: the actual value of
m_someArray[lIndex]
(as opposed to that oflIndex
) might change; m_someArray
in itself might change; and what Neil said about the validity of lIndex's initial value.是的,当前索引的副本将确保两个数组访问使用相同的索引。 但这并不是我真正认为的“线程安全”。 您需要关注对共享变量的并发访问。 在我看来,对数组的访问也可能是一个值得关注的领域。
Yes the copy of the current index will make sure both array accesses use the same index. That is not really what I would have thought of as "thread safe" though. You need to concern yourself with concurrent access to shared variables. To me that looks like the access to the array could be an area of potential concern as well.
这应该是线程安全的(至少在我使用过的所有编译器/操作系统上都是如此)。 不过,为了更加确定,您可以将
m_lCurrentIndex
声明为volatile
。 然后编译器就会知道它随时可能发生变化。This should be thread safe (at least it will on all the compilers/OSes I've worked with). However, to be extra doubly sure you could declare
m_lCurrentIndex
asvolatile
. Then the compiler will know that it might change at any time.这里要问的另一个问题是:从
m_lCurrentIndex
到lIndex
的复制操作是原子操作吗? 如果不是,您最终可能会使用非常奇怪的值,这可能不会有任何好处。 :)要点是:当您使用多个线程时,将无法绕过某种锁定或同步。
Another question to ask here is: is the copy operation from
m_lCurrentIndex
tolIndex
an atomic operation? If it isn’t you might end up using very weird values which will probably do nothing good. :)Point is: when you are using multiple threads there won’t be a way around some kind of locking or synchronization.
本地副本的概念
如果不了解更多细节,就无法回答这个问题。 归结为以下问题:将 m_lCurrentIndex“制作本地副本”到 lIndex 是否是原子的。
假设 x86 并假设 m_lCurrentIndex 是 DWORD 对齐的,并且假设 long 表示 DWORD(在大多数 x86 编译器上都是如此),那么是的,这是原子的。 假设 x64 并假设 long 代表 DWORD 并且 m_lCurrentIndex 是 DWORD 对齐的,或者 long 代表 64b 字并且 m_lCurrentIndex 再次是 64b 对齐是的,这是原子的。 在其他平台上或没有对齐保证时,副本可能需要两次或多次物理读取。
即使本地副本不是原子的,您仍然可以使用 CAS 样式循环使其无锁且线程安全(乐观地假设您可以在不锁定的情况下进行操作,在执行操作后检查是否一切顺利,如果没有,则回滚并再试一次),但这可能需要更多的工作,并且结果将是无锁的,但不是无等待的。
内存障碍
需要注意的是:一旦您向前迈出了一步,您很可能会同时处理多个变量,或者处理充当指针或索引来访问其他共享变量的共享变量。 到那时,事情将开始变得越来越复杂,因为从那时起,您需要考虑读/写重新排序和内存屏障等问题。 另请参阅如何编写无锁结构
Concept of local copy
This question cannot be answered without knowing more details. It boils down into the questions if this "making a local copy" of m_lCurrentIndex into lIndex is atomic.
Assuming x86 and assuming m_lCurrentIndex is DWORD aligned and assuming long represents DWORD (which is true on most x86 compilers), then yes, this is atomic. Assuming x64 and assuming long represents DWORD and m_lCurrentIndex is DWORD aligned or long represents 64b word and m_lCurrentIndex is 64b aligned again yes, this is atomic. On other platforms or without the alignment guarantee two or more physical reads may be required for the copy.
Even without local copy being atomic you still may be able to make it lock-less and thread safe using CAS style loop (be optimistic and assume you can do without locking, after doing the operation check if everything went OK, if not, rollback and try again), but it may be a lot more work and the result will be lock-less, but not wait-less.
Memory barries
A word of caution: once you will move one step forward, you will most likely be handling multiple variables simultaneously, or handling shared variables which act as pointers or indices to access other shared variables. At that point things will start more and more complicated, as from that point you need to consider things like read / write reordering and memory barriers. See also How can I write a lock free structure
是的,它将访问数组的相同元素。 这就像您正在将 m_lCurrentIndex 的值的快照保存到局部变量中。 由于局部变量有自己的内存,因此无论您对 m_lCurrentIndex 执行什么操作,都不会影响局部变量。 但是,请注意,由于赋值操作不能保证是原子的,因此您很可能会在 lIndex 中得到无效值。 如果您从一个线程更新 m_lCurrentIndex 并同时尝试从另一个线程分配到 lIndex,就会发生这种情况。
Yes, it will access the same element of the array. It is like you are taking a snapshot of the value of m_lCurrentIndex into the local variable. Since the local variable has its own memory whatever you do to m_lCurrentIndex will have no effect on the local variable. However, note that since the assignment operation is not guaranteed to be atomic you may very well end up with an invalid value in lIndex. This happens if you update m_lCurrentIndex from one thread and at the same time try the assignement into lIndex from the other thread.