以前我写过一些非常简单的多线程代码,而且我一直都知道,在我正在做的事情中,任何时候都可能会发生上下文切换,所以我一直保护通过共享变量的访问一个 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”关键字(隐式使用内存屏障并提示编译器不要优化此代码)是否使程序员无需在读取 / 之间同步 x86/x64 变量上的 4 字节/8 字节写操作?
问题 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:这是错的,或者如果不是,为什么是对的?
提前感谢您的任何回复:)