sig*_*sen 4 c++ volatile thread-safety
我刚刚问了一个涉及volatile:volatile array c ++的问题
然而,我的问题产生了关于什么volatile做的讨论.
有人声称在使用时CreateThread(),您不必担心volatiles.另一方面,Microsoft提供了一个volatile使用两个线程创建时的示例CreateThread().
我创建在Visual C下面的示例++速成2010年,如果你将其标记不要紧done作为volatile或不
#include "targetver.h"
#include <Windows.h>
#include <stdio.h>
#include <iostream>
#include <tchar.h>
using namespace std;
bool done = false;
DWORD WINAPI thread1(LPVOID args)
{
while(!done)
{
}
cout << "Thread 1 done!\n";
return 0;
}
DWORD WINAPI thread2(LPVOID args)
{
Sleep(1000);
done = 1;
cout << "Thread 2 done!\n";
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD thread1Id;
HANDLE hThread1;
DWORD thread2Id;
HANDLE hThread2;
hThread1 = CreateThread(NULL, 0, thread1, NULL, 0, &thread1Id);
hThread2 = CreateThread(NULL, 0, thread2, NULL, 0, &thread2Id);
Sleep(4000);
CloseHandle(hThread1);
CloseHandle(hThread2);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果done不是,你总能确定线程1会停止volatile吗?
什么volatile:
什么volatile不:
在跨平台C++中不应该依赖的一些非可移植行为:
volatile为防止与其他指令重新排序.其他编译器没有,因为它会对优化产生负面影响.大多数时候,人们真正想要的是栅栏(也称为障碍)和原子指令,如果你有一个C++ 11编译器,或者通过编译器和体系结构相关的函数,它们是可用的.
Fences确保在使用时,所有先前的读/写操作都将完成.在C++ 11中,使用std::memory_order枚举在各个点控制围栏.在VC++中,你可以使用_ReadBarrier(),_WriteBarrier()以及_ReadWriteBarrier()做到这一点.我不确定其他编译器.
在某些体系结构(如x86)上,fence只是一种阻止编译器重新排序指令的方法.在其他人身上,他们可能会发出一条指令来防止CPU本身重新排序.
以下是不当使用的示例:
int res1, res2;
volatile bool finished;
void work_thread(int a, int b)
{
res1 = a + b;
res2 = a - b;
finished = true;
}
void spinning_thread()
{
while(!finished); // spin wait for res to be set.
}
Run Code Online (Sandbox Code Playgroud)
在这里,finished被允许重新排序以之前或者res设定!那么,volatile会阻止与其他volatile的重新排序,对吧?让我们尝试制作每个res易变的:
volatile int res1, res2;
volatile bool finished;
void work_thread(int a, int b)
{
res1 = a + b;
res2 = a - b;
finished = true;
}
void spinning_thread()
{
while(!finished); // spin wait for res to be set.
}
Run Code Online (Sandbox Code Playgroud)
这个简单的例子实际上可以在x86上运行,但效率很低.首先,这个力量res1要在之前设定res2,即使我们并不真正关心它......我们只是希望它们之前都设置好finished.强制此排序之间res1并res2只会阻止有效的优化,在性能蚕食.
对于更复杂的问题,您必须进行每次编写volatile.这会使你的代码膨胀,非常容易出错,并且变得很慢,因为它会阻止比你真正想要的更多的重新排序.
这是不现实的.所以我们使用栅栏和原子.它们允许完全优化,并且只保证在栅栏点完成内存访问:
int res1, res2;
std::atomic<bool> finished;
void work_thread(int a, int b)
{
res1 = a + b;
res2 = a - b;
finished.store(true, std::memory_order_release);
}
void spinning_thread()
{
while(!finished.load(std::memory_order_acquire));
}
Run Code Online (Sandbox Code Playgroud)
这适用于所有架构.res1并且res2可以在编译器认为合适的情况下重新排序操作.执行原子释放可确保所有非原子操作都被排序完成,并且对执行原子获取的线程可见.