编译器优化是否解决了线程安全问题
Are compiler optimization solving thread safety issues?
我正在编写一个C++多线程代码。在测试不同互斥锁的开销时,我发现线程不安全的代码似乎能在Visual Studio中使用Release Configuration编译出正确的结果,但比使用互斥锁的代码快得多。然而,通过调试配置,结果是我所期望的。我想知道是编译器解决了这个问题,还是只是因为在Release配置中编译的代码运行得太快,以至于两个线程永远不会同时访问内存?
我的测试代码粘贴如下。
class Mutex {
public:
unsigned long long _data;
bool tryLock() {
return mtx.try_lock();
}
inline void Lock() {
mtx.lock();
}
inline void Unlock() {
mtx.unlock();
}
void safeSet(const unsigned long long &data) {
Lock();
_data = data;
Unlock();
}
Mutex& operator++ () {
Lock();
_data++;
Unlock();
return (*this);
}
Mutex operator++(int) {
Mutex tmp = (*this);
Lock();
_data++;
Unlock();
return tmp;
}
Mutex() {
_data = 0;
}
private:
std::mutex mtx;
Mutex(Mutex& cpy) {
_data = cpy._data;
}
}val;
static DWORD64 val_unsafe = 0;
DWORD WINAPI safeThreads(LPVOID lParam) {
for (int i = 0; i < 655360;i++) {
++val;
}
return 0;
}
DWORD WINAPI unsafeThreads(LPVOID lParam) {
for (int i = 0; i < 655360; i++) {
val_unsafe++;
}
return 0;
}
int main()
{
val._data = 0;
vector<HANDLE> hThreads;
LARGE_INTEGER freq, time1, time2;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&time1);
for (int i = 0; i < 32; i++) {
hThreads.push_back( CreateThread(0, 0, safeThreads, 0, 0, 0));
}
for each(HANDLE handle in hThreads)
{
WaitForSingleObject(handle, INFINITE);
}
QueryPerformanceCounter(&time2);
cout<<time2.QuadPart - time1.QuadPart<<endl;
hThreads.clear();
QueryPerformanceCounter(&time1);
for (int i = 0; i < 32; i++) {
hThreads.push_back(CreateThread(0, 0, unsafeThreads, 0, 0, 0));
}
for each(HANDLE handle in hThreads)
{
WaitForSingleObject(handle, INFINITE);
}
QueryPerformanceCounter(&time2);
cout << time2.QuadPart - time1.QuadPart << endl;
hThreads.clear();
cout << val._data << endl << val_unsafe<<endl;
cout << freq.QuadPart << endl;
return 0;
}
标准不允许您假设代码默认情况下是线程安全的。在x64的发布模式下编译时,您的代码仍然会给出正确的结果。
但为什么
如果您查看为代码生成的汇编文件,您会发现优化器只是简单地展开循环并应用常量传播。因此,它不是循环65535次,而是在计数器中添加一个常量:
?unsafeThreads@@YAKPEAX@Z PROC ; unsafeThreads, COMDAT
; 74 : for (int i = 0; i < 655360; i++) {
add QWORD PTR ?val_unsafe@@3_KA, 655360 ; 000a0000H <======= HERE
; 75 : val_unsafe++;
; 76 : }
; 77 : return 0;
xor eax, eax
; 78 : }
在这种情况下,每个线程中只有一条非常快速的指令,就不太可能发生数据竞赛:很可能一个线程在下一个线程启动之前就已经完成了。
如何查看基准测试的预期结果
如果希望避免优化器展开测试循环,则需要将_data
和unsafe_val
声明为volatile
。然后您会注意到,由于数据竞争,不安全的值不再正确。用这个修改后的代码运行我自己的测试,我得到了安全版本的正确值,而不安全版本的值总是不同的(错误的)。例如:
safe time:5672583
unsafe time:145092 // <=== much faster
val:20971520
val_unsafe:3874844 // <=== OUCH !!!!
freq: 2597654
想要使您的不安全代码安全吗
如果您想在不使用显式互斥的情况下确保不安全代码的安全,您可以将unsafe_val
设置为atomic
。结果将取决于平台(实现很可能会为您引入互斥),但在与上面相同的机器上,MSVC15处于发布模式,我得到:
safe time:5616282
unsafe time:798851 // still much faster (6 to 7 times in average)
val:20971520
val_unsafe:20971520 // but always correct
freq2597654
然后您还必须做的唯一一件事是:将变量的原子版本从unsafe_val
重命名为also_safe_val
;-)
相关文章:
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 在std::thread中,joinable()然后join()线程安全吗
- 在c++队列中使用pop和visit实现线程安全
- 以线程安全的方式调用"QQuickPaintedItem::updateImage(const QImage&image)"(no QThread)
- 全局变量 多读取器 一个写入器多线程安全?
- 共享队列的线程安全
- boost::文件系统::recursive_directory_iterator多线程安全
- 以线程安全的方式转换 C/C++ 中时区名称字符串的时区偏移量
- 线程安全运算符<<
- 如何使缓存线程安全
- C++线程安全:如果只有一个线程可以写入非原子变量,但多个线程从中读取. 会遇到问题吗?
- 提升精神 V2 Qi 语法线程安全吗?
- asio 链对象线程安全吗?
- 线程安全队列 c++
- 提供对不同类型的数据(建议、代码审查)的线程安全访问的类
- 如何以线程安全的方式更改目录?
- 线程安全的引用计数队列C++
- 析构函数和线程安全
- 适用于大型数组的无复制线程安全环形缓冲区