原子操作與原子型別

飛羚發表於2020-12-17

  多執行緒是併發程式設計中不可或缺的部分,而所謂原子操作,也就是指多執行緒中“最小的且不可並行化的”操作,如果一個共享資源的操作時原子操作的話,意味著多個執行緒訪問該資源時,有且僅有一個執行緒在對這個資源進行操作。通常情況下,原子操作都是通過“互斥”來實現的,例如mutex。這種操作在開發當中使用相當頻繁所以我們不做討論。但是是否還有其他更便於使用的方式呢?c++11給出了答案,就是原子型別。

  原子型別,是c++11中提供的一類資料型別,這種資料型別,不需要我們顯式使用互斥鎖,但是,編譯器卻可以保證原子型別線上程間被互斥地訪問。我們可以理解為:c++11將原子型別的互斥鎖、臨界區給抽象化了,而不需要我們再去進行相關操作。

  我們可以像下面這樣定義一個原子型別:

std::atomic<T> t;

  其中,atomic是原子型別的類别範本,但是,c++11同樣也提供了一些內建型別方便直接使用,內建型別如下:
  在這裡插入圖片描述
  原子型別可以直接對普通型別進行賦值,也就是說,下述操作是支援的:

atomic<float> af{1.2f};
float f = af;

  這是因為atomic類别範本定義了從atomic到T的型別轉換函式的緣故,在需要時,會進行隱式轉換。但是需要注意:原子型別不支援拷貝構造。
  我們已經定義了原子型別,但是使原子型別真正實現線上程中互斥使用的還是原子操作,c++11在atomic模板類中實現了統一的介面,以完成讀、寫、交換等。對於內建型別而言,主要是通過過載一些全域性操作符完成的,下表顯示了atomic型別的基本原子操作。
  在這裡插入圖片描述
  這裡的atomic-integral-type和integral-type指的是內建型別,class-type則是指自定義型別。從表中可以看到,對於大多數的原子型別而言,都支援讀(load)、寫(store)、交換(exchange)等操作。通常情況下,這些已經足夠使用了。
  這裡需要注意兩點,在進行原子操作的讀寫時,仍支援直接賦值,如下:

atomic<int> a;
int b = a; //#1

a = 1; //#2

  其中,#1等同於呼叫b = a.load(), #2等同於a.store(1),所以上述操作仍是原子操作。
  原子型別及原子操作介紹基本完畢,下面是其相關使用程式碼的示例:

#include <thread>
#include <atomic>
#include <iostream>
#include <unistd.h>

using namespace std;

atomic_llong total{0};

void func(int)
{
    for(long long i = 0 ; i < 1000000 ; ++i)
    {
        total += i;
    }
}

int main()
{
    thread t1(func, 0);
    thread t2(func, 0);

    t1.join();
    t2.join();

    cout << total << endl;
    return 0;
}

相關文章