C++11 多线程 thread, lambda, CPU周期
2015-12-29 13:07
337 查看
本博客讲了一段代码,显示多线程时候,临界区域如果不上锁会发生的问题。
加入头文件#include < thread >就可以使用多线程了,加入头文件#include < mutex >,就可以使用锁了。在多线程中使用lambda,看上去十分”酷炫“。更多关于C++11的lambda
vector里面有五个线程,每一个线程使用lambda的方式描述:对同一个对象counter调用同一个increment()方法一万次。
如果不上锁,把相关代码注释掉,如下:
再运行一次,输出结果是36378(每一次运行结果还不一样!)
为什么这样呢?为了彻底从底层解释清楚,我先引入一个概念:CPU周期。CPU周期是计算机操作的最小单位时间。假设我的电脑的CPU频率是2GHz,那么我的电脑的CPU周期是0.5纳秒(1纳秒 = 10−910^{-9} 秒)。
我想:既然是最小单位时间,那么可以认为:计算机无法分辨出时间间隔在一个CPU周期之内的操作。以上述的代码为例:有两个相同的线程相隔0.2纳秒(小于0.5纳秒)先后进入临界区counter.increment()方法,那么计算机会把这两个线程”当成一个线程“看待,因为计算机无法分辨出这两个线程。这么说,时间如果可以无限细分的话,就不存在”同时进入了”,但是对于计算机,时间间隔小于一个CPU周期,就可以看成“同时”。
再次回头看看上述的代码,5个线程,每一线程都要进入临界区域counter.increment() 一万次,总共五万次。但是五万次中,有一些是“同时进入”的,即时间间隔非常非常小,小于一个CPU周期,0.5纳秒。那么有些时候,本来可以increment( )多次的,但是现在仅仅increment( ) 一次,多次减少到一次,所以输出的结果小于5万。
这就是多线程可能引起的程序的“不确定性”,这些不确定性给程序带来了难以调试的bug。在本博客中的例子中,解决方案是在临界区域的前后加上一个锁 mutex,进入之前 mutex.lock( ),出去之后mutex.unlock( )就可以了。这样就可以防止线程同时进入。
注:这是我为了解释程序运行的结果小于5万,而自己想(YY)出来的“理论”,不一定是真理。至少看上去“自圆其说”,可以解释表面现象。如果有专家发现我的想法错误,欢迎指出~
加入头文件#include < thread >就可以使用多线程了,加入头文件#include < mutex >,就可以使用锁了。在多线程中使用lambda,看上去十分”酷炫“。更多关于C++11的lambda
Talk is cheap, show me the code
[code]#include <iostream> #include <thread> #include <vector> #include <mutex> using namespace std; struct Counter { mutex m; int value; Counter() : value(0){}; void increment() { m.lock(); ++value; // 临界区域 m.unlock(); } }; int main() { Counter counter; vector<thread> threads; for (int i = 0; i < 5; ++i) { threads.push_back(thread([&]() { for (int i = 0; i < 10000; ++i) { counter.increment(); } })); } for (auto &thread : threads) { thread.join(); } cout << counter.value << endl << endl; system("pause"); return 0; }
代码讲解
按照上述代码运行,输出结果是50000(5个线程,每一线程都increment()一万次,所以5×\times10000 = 50000)。vector里面有五个线程,每一个线程使用lambda的方式描述:对同一个对象counter调用同一个increment()方法一万次。
如果不上锁,把相关代码注释掉,如下:
[code]void increment() { //m.lock(); ++value; //m.unlock(); }
再运行一次,输出结果是36378(每一次运行结果还不一样!)
为什么这样呢?为了彻底从底层解释清楚,我先引入一个概念:CPU周期。CPU周期是计算机操作的最小单位时间。假设我的电脑的CPU频率是2GHz,那么我的电脑的CPU周期是0.5纳秒(1纳秒 = 10−910^{-9} 秒)。
我想:既然是最小单位时间,那么可以认为:计算机无法分辨出时间间隔在一个CPU周期之内的操作。以上述的代码为例:有两个相同的线程相隔0.2纳秒(小于0.5纳秒)先后进入临界区counter.increment()方法,那么计算机会把这两个线程”当成一个线程“看待,因为计算机无法分辨出这两个线程。这么说,时间如果可以无限细分的话,就不存在”同时进入了”,但是对于计算机,时间间隔小于一个CPU周期,就可以看成“同时”。
再次回头看看上述的代码,5个线程,每一线程都要进入临界区域counter.increment() 一万次,总共五万次。但是五万次中,有一些是“同时进入”的,即时间间隔非常非常小,小于一个CPU周期,0.5纳秒。那么有些时候,本来可以increment( )多次的,但是现在仅仅increment( ) 一次,多次减少到一次,所以输出的结果小于5万。
这就是多线程可能引起的程序的“不确定性”,这些不确定性给程序带来了难以调试的bug。在本博客中的例子中,解决方案是在临界区域的前后加上一个锁 mutex,进入之前 mutex.lock( ),出去之后mutex.unlock( )就可以了。这样就可以防止线程同时进入。
注:这是我为了解释程序运行的结果小于5万,而自己想(YY)出来的“理论”,不一定是真理。至少看上去“自圆其说”,可以解释表面现象。如果有专家发现我的想法错误,欢迎指出~
相关文章推荐
- C++ 中获取 可变形參函数中的參数
- c/c++指针基础使用
- C++变量的存储持续性、作用域和链接性
- VS2010 c/c++ 本地化 emscripten 配置
- 生命游戏(c语言)
- C++11 sort, vector, lambda——vector 自定义排序
- 梯度下降法的C语言实现
- PC-Lint与CC++代码质量
- 在Visual Studio 2013 中使用C++单元测试
- C/C++语言的存储类型
- C++指针比较的问题
- 微软应该开源的15款产品,记事本、VC++ 在列
- Effective C++:条款25:考虑写出一个不抛异常的swap函数
- C/C++异常捕获try{}catch(...) // __try{ //...}__except(EXCEPTION_EXECUTE_HANDLER){ //...}
- eclipse中编写运行c/c++
- c/c++中动态内存分配处理字符串的细节问题
- 获取目录下所有文件(C/C++)
- CTBCAFBridge hpp 和 cpp 文件分析
- c++下new与delete基础用法
- ROS学习笔记(五)::RVIZ:Markers::Points and Lines (C++)