详解c++多线程(四)

C++中的原子操作

一、atomic模版函数

为了避免多个线程同时修改全局变量,C++11除了提供互斥量mutex这种方法以外,还提供了atomic模版函数。

使用atomic可以避免使用锁,而且更加底层,比mutex效率更高。

为了方便使用,c++11为模版函数提供了别名。

atomic<bool>   别名:atomic_bool  

atomic<int>  别名:atomic_int

atomic<char> 别名:atomic_char 

atomic<long> 别名:atomic_long 

 

我们先来看一个例子:

#include <thread>
#include <iostream>
#include <vector>
#include <atomic>

using namespace std;

void func(int& counter)
{
    for (int i = 0; i < 100000; ++i)
    {
        ++counter;
    }
}

int main()
{
    //atomic<int> counter(0);
    int counter = 0; //新建一个整型原子counter,将counter初始化为0
    vector<thread> threads;
    for (int i = 0; i < 10; ++i)
    {
        threads.push_back(thread(func, ref(counter)));
    }
    for (auto& current_thread : threads)
    {
        current_thread.join();
    }
    cout << "Result = " << counter << '\n';
    return 0;
}

输出结果:

 

显然这个结果不是我们想要的,多跑几次就会发现,每一次的结果都会不一样。而这段代码的问题就在于多个线程同时修改了counter这个数导致出现错误。

了解了前几章以后知道了锁可以用来解决这个问题,但是其实原子类型可以更加方便得解决这个问题。

只需要把counter的原来的int型,改为atomic_int型就可以了,非常方便,也不需要用到锁。

 

#include <thread>
#include <iostream>
#include <vector>
#include <atomic>

using namespace std;

void func(atomic_int& counter)
{
    for (int i = 0; i < 100000; ++i)
    {
        ++counter;
    }
}

int main()
{
    //atomic<int> counter(0);
    atomic_int counter(0); //新建一个整型原子counter,将counter初始化为0
    vector<thread> threads;
    for (int i = 0; i < 10; ++i)
    {
        threads.push_back(thread(func, ref(counter)));
    }
    for (auto& current_thread : threads)
    {
        current_thread.join();
    }
    cout << "Result = " << counter << '\n';
    return 0;
}

输出结果: 

结果就正确了。

二、std::atomic_flag

std::atomic_flag是一个原子型的布尔变量,只有两个操作:

1)test_and_set,如果atomic_flag 对象已经被设置了,就返回True,如果未被设置,就设置之然后返回False

2)clear,把atomic_flag对象清掉

注意这个所谓atomic_flag对象其实就是当前的线程。如果当前的线程被设置成原子型,那么等价于上锁的操作,对变量拥有唯一的修改权。

调用clear就是类似于解锁。

来看一个例子:

#include <iostream>
#include <atomic>
#include <vector>
#include <thread>
#include <sstream>


std::atomic_flag lock = ATOMIC_FLAG_INIT; //初始化原子flag
std::stringstream  stream;

void append_number(int x)
{
    while(lock.test_and_set()); //如果原子flag未设置,那么返回False,就继续后面的代码。否则一直返回True,就一直停留在这个循环。
    stream<<"thread#" <<x<<'\n';
    lock.clear(); //去除flag的对象
}

int main()
{
    std::vector<std::thread> threads;
    for(int i=0;i<10;i++)
        threads.push_back(std::thread(append_number, i));
    
    for(auto& th:threads)
        th.join();
    std::cout<<stream.str()<<'\n';
}

再看一个例子:

#include <iostream>
#include <atomic>
#include <vector>
#include <thread>
#include <sstream>

using namespace std;

atomic<bool> ready(false);
atomic_flag winner = ATOMIC_FLAG_INIT;

void count1m(int id)
{
    while(!ready) //如果ready=false,就会让当前线程一直在等待状态
        this_thread::yield();
    
    //此时ready为true
    for(int i=0;i<1000;i++);//数数
    
    //当有某个线程结束计数,然后被flag设置成了原子线程则返回false,于是执行打印id语句
    //由于并没有clear,所以该线程会一直是原子线程,而其他线程调用test_and_set就会一直返回True,于是不会执行后面的打印语句
    if(!winner.test_and_set())
        cout<<"winner thread id = "<<id<<endl;
}

int main()
{
    vector<thread> threads;
    for(int i=0;i<10;i++)
        threads.push_back(thread(count1m, i));
    ready = true;
    
    for(auto &th:threads)
        th.join();
}

参考:

https://blog.csdn.net/yhc166188/article/details/80572108

https://www.cnblogs.com/taiyang-li/p/5914331.html

猜你喜欢

转载自www.cnblogs.com/corineru/p/10867079.html