C++多執行緒:atomic

sgqmax發表於2024-11-01

在許多為了效能和效率的場景下,需要開發一些lock-free的演算法和資料結構

atomic_flag

原子布林型別,只支援 test-and-set 和 clear 操作

建構函式

atomic_flag()noexcept=default;
atomic_flag(const atomic_flag&)=delete;

只有預設建構函式,而不能從其他物件構造
atomic_flag物件需要使用ATOMIC_FLAG_INIT初始化,使其處於 clear 狀態,否則是unspecified未指定的

建立10個執行緒用於計數,先完成計數任務的執行緒輸出id

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

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

void count1m(int id){
  while(!ready){
    std::this_thread::yield();
  }
  for(int i=0; i<10000; ++i){
    // count
  }
  // 執行完後,該函式
  if(!winner.test_and_set()){
    std::cout<<"thread "<<id<<" first\n";
  }
};

int main(){
  std::vector<std::thread> ths;
  std::cout<<"spawning 10 threads that count to 1 million..."<<std::endl;

  for(int i=1; i<=10; ++i){
    ths.push_back(std::thread(count1m, i));
  }
  ready= true;

  for(auto& th:ths){
    th.join();
  }

  return 0;
}

std::atomic_flag::test_and_set

函式原型如下:

bool test_and_set(memory_order sync=memory_order_seq_cst)volatile noexcept;
bool test_and_set(memory_order sync=memory_order_seq_cst)noexcept;

用於檢查標誌,若std::atomic_flag之前被設定過,則返回true,否則返回false並設定標誌
該操作為原子操作,可以指定的Memory Order如下

型別
memory_order_relaxed Relaxed
memory_order_consume Consume
memory_order_acquire Acquire
memory_order_release Release
memory_order_acq_rel Acquire/Release
memory_order_seq_cst Sequentially consistent
#include<iostream>
#include<atomic>
#include<thread>
#include<vector>
#include<sstream>

std::atomic_flag lock_stream=ATOMIC_FLAG_INIT;
std::stringstream stream;

void append_number(int x){
  while(lock_stream.test_and_set()){}
  stream<<"thread "<<x<<'\n';
  lock_stream.clear();
}

int main(){
  std::vector<std::thread> ths;
  for(int i=1; i<=10; ++i){
    ths.push_back(std::thread(append_number,i));
  }
  for(auto& th:ths){
    th.join();
  }
  std::cout<<stream.str()<<std::endl;
  return 0;
}

std::atomic_flag::clear

清除物件標誌,即設定為false
函式原型如下:

void clear(memory_order sync=memory_order_seq_cst)volatile noexcept;
void clear(memory_order sync=memory_order_seq_cst)noexcept;

該函式也可以指定Memory Order的值

std::atomic_flag可以當作一個簡單的自旋鎖使用

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

std::atomic_flag lock= ATOMIC_FLAG_INIT;

void f(int n){
  for(int cnt=0; cnt<100; ++i){
    while(lock.test_and_set(std::memory_order_acquire))
    ; //spin
    std::cout<<"output from thread "<<n<<'\n';
    lock.clear(std::memory_order_release);
  }
}

int main() {
  std::vector<std::thread> ths;
  for(int n=0; n<10; ++n){
    ths.emplace_back(f,n);
  }
  for(auto& th:ths){
    th.join();
  }

  return 0;
}

std::atomic_flag的加鎖操作可以理解為lock.test_and_set(std::memory_order_acquire);
加鎖操作時,返回false表示加鎖成功
因為此前lock的標誌為false,呼叫test_and_set()後標誌為true,說明某一執行緒獲得了鎖
std::atomic_flag的解鎖操作相當於lock.test_and_set(std::memory_order_release);

atomic

原子型別atomic_flag過於簡單,下面介紹功能更完善的std::atomic
原子型別物件的特點:不同執行緒訪問不會導致資料競爭data race問題

建構函式

std::atomic是一個模板類

template<class T>
struct atomic;

template<>
struct atomic<integral>{};

template<class T>
struct atomic<T*>{};

標準庫提供了對整型和指標型別的特化實現

預設建構函式
atomic()noexcept=default;
物件處於未初始化狀態,需要使用atomic_init進行初始化

初始化建構函式
constexpr atomic(T val)noexcept;
可用T物件對atomic進行初始化

複製建構函式
atomic(const atomic&)=delete;
被禁用,不可複製

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

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

void do_count1m(int id){
  while(!ready){
    std::this_thread::yield();
  }
  for(volatile int i=0; i<1000000; ++i){
  }
  if(!winner.test_and_set()){
    std::cout<<"thread "<<id<<" first\n";
  }
}

int main(){
  std::vector<std::thread> ths;
  std::cout<<"spawning 10 threads that count to 1 million...\n";
  for(int i=1; i<=10; ++i){
    ths.push_back(std::thread(count1m,i));
  }
  ready= true;
  for(auto& th:ths){
    th.join();
  }
  return 0;
}

成員函式

賦值操作
std::atomic::operator=()函式

copy (deleted)

atomic& operator=(const atomic&)=delete;
atomic& operator=(const atomic&)volatile=delete;

賦值運算是被禁用的

set value

T operator=(T val)noexcept;
T operator=(T val)volatile noexcept;

過載了賦值運算子,使得原子型別可以被型別為T的變數賦值,類似於隱式轉換
該操作是原子操作,記憶體序預設為順序一致性std::memory_order_seq_cst
若需要使用其他記憶體序,可以使用std::atomic::store()

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

std::atomic<int> foo=0;

void set_foo(int x){
  foo=x;
}
void print_foo(){
  while(foo==0){
    std::this_thread::yield();
  }
  std::cout<<"foo:"<<foo<<std::endl;
}
int mian(){
  std::thread first(print_foo);
  std::thread second(set_foo, 10);
  first.join();
  second.join();
  return 0;
}

常用成員函式

bool is_lock_free()const volatile noexcept;
bool is_lock_free()const noexcept;

判斷物件是否有lock-free特性
若物件具有該特性,則多執行緒訪問該物件時不會導致執行緒阻塞

void store(T val, memory_order sync=memory_order_seq_cst)volatile noexcept;
void store(T val, memory_order sync=memory_order_seq_cst)noexcept;

修改被封裝的值,將val複製給原子物件所封裝的值,sync指定記憶體序

型別
memory_order_relaxed Relaxed
memory_order_release Release
memory_order_seq_cst Sequentially consistent

示例如下

#include<iostream>
#include<atomic>
#include<thread>
std::atomic<int> foo(0);
void set_foo(int x){
  foo.store(x, std::memory_order_relaxed);
}
void print_foo(){
  int x;
  do{
    x= foo.load(std::memory_order_relaxed);
  }while(x==0);
  std::cout<<"foo:"<<x<<std::endl;
}
int main(){
  std::thread first(print_foo);
  std::thread second(set_foo, 10);
  first.join();
  second.join();
  return 0;
}
T load(memory_order sync=memory_order_seq_cst)const volatile noexcept;
T load(memory_order sync=memory_order_seq_cst)const noexcept;

讀取被封裝的值,引數sync設定記憶體序

型別
memory_order_relaxed Relaxed
memory_order_consume Consume
memory_order_acquire Acquire
memory_order_seq_cst Sequentially consistent

示例如下

#include<iostream>
#include<atomic>
#include<thread>
std::atomic<int> foo(0);
void set_foo(int x){
  foo.store(x, std::memory_order_relaxed);
}
void print_foo(){
  int x;
  do{
    x= foo.load(std::memory_order_relaxed);
  }while(x==0);
  std::cout<<"foo:"<<x<<'\n';
}
int main(){
  std::thread first(print_foo);
  std::thread second(set_foo, 10);
  first.join();
  second.join();
  return 0;
}
operator T()const volatile noexcept;
operator T()const noexcept;

load功能類似,讀取封裝的值,預設記憶體序是std::memory_order_seq_cst

示例如下

T exchange(T val, memory_order sync=memory_order_seq_cst)volatile noexcept;
T exchange(T val, memory_order sync=memory_order_seq_cst)noexcept;
compare_exchange_weak
compare_exchange_strong

1

std::atomic對整型和指標型別特化

成員函式
fetch_add
fetch_sub
fetch_and
fetch_or
fetch_xor
operator++
operator--

2

C++11原子操作中C風格的API
std::atomic和std::atomic_flag

相關文章