【发布时间】:2010-03-31 10:46:10
【问题描述】:
之前我写过一些非常简单的多线程代码,而且我一直都知道,在我正在做的事情中,任何时候都可能发生上下文切换,所以我一直保护着访问通过 CCriticalSection 类共享变量,该类在构造时进入临界区并在销毁时将其保留。我知道这是相当激进的,我经常进入和离开关键部分,有时甚至非常严重(例如,在函数开始时,我可以将 CCriticalSection 放在更紧凑的代码块中)但我的代码不会崩溃并且运行速度足够快.
在工作中,我的多线程代码需要更紧密,只需在所需的最低级别锁定/同步。
在工作中我试图调试一些多线程代码,我遇到了这个:
EnterCriticalSection(&m_Crit4);
m_bSomeVariable = true;
LeaveCriticalSection(&m_Crit4);
现在,m_bSomeVariable 是一个 Win32 BOOL(非易失性),据我所知,它被定义为一个 int,并且在 x86 上读取和写入这些值是一条指令,并且由于上下文切换发生在指令边界,则无需将此操作与临界区同步。
我在网上做了一些研究,看看这个操作是否不需要同步,我想出了两个场景:
- CPU 实现乱序执行或第二个线程在不同的内核上运行并且更新的值没有写入 RAM 以供其他内核查看;和
- int 不是 4 字节对齐的。
我相信数字 1 可以使用“volatile”关键字来解决。在 VS2005 及更高版本中,C++ 编译器使用内存屏障围绕对这个变量的访问,确保在使用之前始终将变量完全写入/读取到主系统内存。
数字 2 我无法验证,我不知道为什么字节对齐会产生影响。我不知道 x86 指令集,但 mov 是否需要被赋予 4 字节对齐的地址?如果不需要,您是否需要使用指令组合?这会带来问题。
所以...
问题 1: 是否使用“volatile”关键字(隐含地使用内存屏障并提示编译器不要优化此代码)使程序员无需同步 4 字节/8 -byte 在读/写操作之间的 x86/x64 变量上?
问题 2: 是否明确要求变量是 4 字节/8 字节对齐的?
我对我们的代码和类中定义的变量做了更多的研究:
class CExample
{
private:
CRITICAL_SECTION m_Crit1; // Protects variable a
CRITICAL_SECTION m_Crit2; // Protects variable b
CRITICAL_SECTION m_Crit3; // Protects variable c
CRITICAL_SECTION m_Crit4; // Protects variable d
// ...
};
现在,对我来说,这似乎太过分了。我认为关键部分在进程之间同步线程,所以如果你有一个你可以输入它并且该进程中没有其他线程可以执行。对于要保护的每个变量,都不需要一个临界区,如果你在临界区,那么没有其他东西可以打断你。
我认为唯一可以从临界区外部更改变量的是进程是否与另一个进程共享内存页面(你可以这样做吗?)并且另一个进程开始更改值。互斥体在这里也有帮助,命名互斥体是跨进程共享的,还是只有同名的进程?
问题 3: 我对关键部分的分析是否正确,是否应该重写此代码以使用互斥锁?我看过其他同步对象(信号量和自旋锁),它们更适合这里吗?
问题 4: 关键部分/互斥体/信号量/自旋锁在哪里最适合?也就是说,它们应该应用于哪个同步问题。选择一个而不是另一个会带来巨大的性能损失吗?
在我们讨论的过程中,我读到自旋锁不应该在单核多线程环境中使用,只能在多核多线程环境中使用。那么,问题 5:这是错的吗?如果不是,为什么是对的?
提前感谢您的任何回复:)
【问题讨论】:
-
你能提供自旋锁论文的来源吗!?
标签: c++ multithreading mutex volatile memory-barriers