可怜的volatile,被误解如此之深,就不应该出现在本章,因为它和并发编程毫无关系。但在其他语言(java或者c#),它在并发编程中很用。在某些c++编辑器中,volatile被赋予了特殊语义,使其可以用在并发编程中(但也只有用这些编译器编译才可以用)。即使为了澄清围绕它产生的误解,在关于并发编程的本章中探讨一下volatiile也是有价值的。
c++容易混淆属于volatile和std::atoimic模板的特征。模板的实例化(如std::atoimic<int> std::atomic<bool>和std::atomic<Widget*>)提供了不同线程之间的原子操作。一旦std::atomic对象构建出来,对其访问,就如同在mutex保护的关键代码中访问一样。然而这些操作由特殊的机器指令来实现,比使用mutex保护的效率要高很多。
考虑下面使用std::atomic的代码:
std::atomic<int> ai(0); //初始化ai为0
ai = 10; //原子设置ai为10
std::cout << ai; //原子打印ai
ai++; // 原子增加ai到11
--ai ; //原子减小ai到10
在这些语句操作过程中,别的线程读取ai的值只可能为(0,10,11,10)。别的值都不可能出现(当然我们假定了只有一个线程更改)
上面例子中两个语句值得一提。一个是std::cout << ai。因为事实上std::atomic只保证读取ai是原子操作,并不保证整条语句都是原子的。在读取ai和<<操作输出到标准输出之间,别的线程可能会更改ai的值。这对输出无影响,因为int 的<<操作是值传递(所以智慧输出读取到的值)。但重要的是要理解,这条语句中的原子操作不过是单指读取ai。
另外一个值得一提的是最后两个语句的行为——自增和自减。它们每个都是读改写(RMW)操作,因此它们进行原子操作。这是std::atomic类型最优雅之处,一旦一个std::atomic对象创建出来,所有成员函数操作,包括自增和自减之类的(RMW)都是原子操作。
相反,多线程上下文使用volatile什么都不会保证
volatile int vi(0);//初始化为0
vi=10; //设置vi为10
std::cout << vi