1. 程式人生 > >詳解c++多線程(四)

詳解c++多線程(四)

pen fun 需要 back oid info logs 結束 www.

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

詳解c++多線程(四)