您的位置:首页 > 编程语言 > C语言/C++

C++11 多线程 thread, lambda, CPU周期

2015-12-29 13:07 337 查看
本博客讲了一段代码,显示多线程时候,临界区域如果不上锁会发生的问题。

加入头文件#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)出来的“理论”,不一定是真理。至少看上去“自圆其说”,可以解释表面现象。如果有专家发现我的想法错误,欢迎指出~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: