【C++】多线程(二):std::mutex std::atomic的使用

这篇文章接着上一篇,继续介绍C++中的多线程。

推荐先阅读上一篇 【C++】多线程(一):std::thread的使用

互斥

我们前面的函数,无论是线程之间,还是线程和主线程之间,都是没有数据交换的。 接下来让多个线程操作一个全局变量试试。

int global_num = 0;

void plus1000()
{
    for (int i = 0; i < 1000; i++)
        global_num++;
}

int main()
{
    thread ths[10];
    for (auto &th : ths)
        th = thread(plus1000);

    for (auto &th : ths)
        th.join();
    cout << "n = " << global_num << endl;
    return 0;
}

运行结果并不是固定的,很奇怪。只有第一次出现了结果异常的,后续都是正常的。即使我把生成的 exe 删掉,结果也是正常的。

[Running] cd "d:\Codes\CPP\VSCodeProjects\Nov\ThreadTest\" && g++ ThreadTest.cpp -o ThreadTest && "d:\Codes\CPP\VSCodeProjects\Nov\ThreadTest\"ThreadTest
n = 9059

[Done] exited with code=0 in 3.399 seconds

[Running] cd "d:\Codes\CPP\VSCodeProjects\Nov\ThreadTest\" && g++ ThreadTest.cpp -o ThreadTest && "d:\Codes\CPP\VSCodeProjects\Nov\ThreadTest\"ThreadTest
n = 10000

[Done] exited with code=0 in 1.189 seconds

[Running] cd "d:\Codes\CPP\VSCodeProjects\Nov\ThreadTest\" && g++ ThreadTest.cpp -o ThreadTest && "d:\Codes\CPP\VSCodeProjects\Nov\ThreadTest\"ThreadTest
n = 10000

如果这里针对全局变量的操作是单线程的,就不会有数据异常的问题。要解决这个问题,需要用到两个变量。

std::mutex

mutex又称互斥量,C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在 mutex 头文件中,所以如果你需要使用 std::mutex,就必须包含 mutex 头文件。然后在适当的地方声明一个 mutex 变量即可。

mutex mtx;

在操作全局变量前对 mutex 上锁,操作完解锁,就可以避免数据异常的问题。

void plus1000()
{
    for (int i = 0; i < 1000; i++)
    {
        mtx.lock();
        global_num++;
        mtx.unlock();
    }
}

不停地加锁和解锁会消耗 CPU 的性能,也会延长程序的运行时间。C++ 有很多对程序进行计时的函数,我们这里使用 Windows 平台的一个函数。

QueryPerformanceCounter()是一个Windows API,所需头文件为

这个函数返回高精确度性能计数器的值,它可以以微妙为单位计时.但是QueryPerformanceCounter()
确切的精确计时的最小单位是与系统有关的,

所以,必须要查询系统以得到QueryPerformanceCounter()返回的嘀哒声的频率.
QueryPerformanceFrequency() 提供了这个频率值,返回每秒嘀哒声的个数.

void plus1000()
{
    for (int i = 0; i < 1000; i++)
    {
        mtx.lock();
        global_num++;
        mtx.unlock();
    }
}

int main()
{
    LARGE_INTEGER t1,t2,tc;
    QueryPerformanceFrequency(&tc);
    QueryPerformanceCounter(&t1);
    thread ths[10];
    for (auto &th : ths)
        th = thread(plus1000);

    for (auto &th : ths)
        th.join();
    QueryPerformanceCounter(&t2);
    cout << "n = " << global_num << ", total time = " << (double)(t2.QuadPart-t1.QuadPart)/(double)tc.QuadPart << endl;
    return 0;
}

输出的结果为

n = 10000, total time = 0.0003675

std::atomic

atom 意为 原子,即不可分割的最小操作。理解这个需要一点操作系统多线程的知识。

回到 C++ 来说,需要引入头文件atomic,将 int 的声明方式改为 atomic_int 或者atomic

atomic_int global_num = 0;

这两个是一样的,从源代码可以看出来:

  /// atomic_int
  typedef atomic<int>			atomic_int;

即可去掉 mutex 锁,依然 能保证多线程下数据不会出现异常。这时的输出为:

n = 10000, total time = 0.0003647

感觉其实也没差多少,我更愿意理解为误差。

async 异步

thread在使用的时候有个很大的问题,就是没法获取函数的返回值。(不过,如果你是一个写过一些 C++ 代码的人,你应该熟悉使用引用参数替代返回值的写法,这里就不展开了),另外功能上也没有 async 全面。

事实上,thread 是一定会创建一个新的线程的,但 async 不一定,这在系统资源紧张的时候尤为明显,此时强行创建一个新的线程有概率导致程序崩溃。总之,先写个程序吧。

#include 
#include 

using namespace std;

int main()
{
    async([]
          { cout << "Maybe a new thread?" << endl; });

    cout << "Yeah, u r right!" << endl;
    return 0;
}

和 thread 不一样,async 是个函数,其声明如下:

    async(_Fn&& __fn, _Args&&... __args)
    {
      return std::async(launch::async|launch::deferred,
			std::forward<_Fn>(__fn),
			std::forward<_Args>(__args)...);
    }
    
	async(launch __policy, _Fn&& __fn, _Args&&... __args)

第一个参数即你希望 async 以什么样的方式执行 fun,

标识符 作用
launch::async 开启一个新线程立刻执行fun
launch::deferred 不立刻执行fun,而是延迟到调用获取结果get的时候再执行,此时也不会开启新的线程
std::launch::async or std::launch::deferred 由操作系统决定采用以上哪种方式,当没有传入launch参数时,此为参数的默认值
  /// Launch code for futures
  enum class launch
  {
    async = 1,
    deferred = 2
  };

你可能感兴趣的:(c++,开发语言)