C++:std::atomic

atomic 原子操作:

原子:即最小且不可分割的执行单元,我们知道高级语言一条命令在底层可能分解为多条命令执行,在多线程时,可能会造成无可预料的问题。

    如果某些命令的操作是不可分割的,意思就是只有两种状态,完成和未完成,这样就解决了多线程同时访问导致的问题。

优势:解决多线程冲突的方法最早可以通过各种锁来实现,但锁的效率不高,引入了原子操作后可以实现多线程间的无锁同步。

  互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量

  原子变量既不可复制亦不可移动。

 

atomic_flag

最简单的原子变量实例

先用这个类型来讲解原子变量。

atomic_flag其实是一个bool类型的原子变量,可以设置该变量为true 或 false,获取该变量的值等。

接口介绍:

ATOMIC_FLAG_INIT:用于给atomic_flag变量赋初值,如果定义后为赋值,则状态是不确定的。被这个赋值后的状态为false。

test_and_set() :接口函数,调用后状态变为true,并返回改变状态前的状态值。

clear() : 接口函数,调用后状态变为false。

应用1:实现自选锁,代码来自cppreference

#include <thread>
#include <vector>
#include <iostream>
#include <atomic>
 
std::atomic_flag lock = ATOMIC_FLAG_INIT;

int gcnt = 0;
void f(int n)
{
    for (int cnt = 0; cnt < 100; ++cnt) {
        while (lock.test_and_set(std::memory_order_acquire))  // 获得锁
             ; // 自旋
        std::cout << "Output from thread " << n << '\n';
        gcnt++;
        lock.clear(std::memory_order_release);               // 释放锁
    }
}
 
int main()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n) {
        v.emplace_back(f, n);
    }
    for (auto& t : v) {
        t.join();
    }
    
    std::cout << “All output command count :” << gcnt << std::endl;
}

 自旋锁的解释:当某一个线程调用‘lock.test_and_set’时,即设置lock的状态为true,当另一个线程进入时,再次调用test_and_set时返回的状态为true,则一直在while循环中不断获取,即实现了等待,知道第一个线程调用clear将状态变为false。

 

应用2:多线程竞争

 以下例子展示五锁实现多线程同步

#include <iostream>              // std::cout
#include <atomic>                // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
#include <thread>                // std::thread, std::this_thread::yield
#include <vector>                // std::vector

std::atomic<bool> ready(false);    // can be checked without being set
std::atomic_flag winner = ATOMIC_FLAG_INIT;    // always set when checked

void count1m(int id)
{
    while (!ready) {
        std::this_thread::yield();
    } // 等待主线程中设置 ready 为 true.

    // 如果某个线程率先执行完上面的计数过程,则输出自己的 ID.
    // 此后其他线程执行 test_and_set 是 if 语句判断为 false,
    // 因此不会输出自身 ID.
    if (!winner.test_and_set()) {
        std::cout << "thread #" << id << " won!\n";
    }
};

int main()
{
    std::vector<std::thread> threads;
    for (int i = 1; i <= 1000; ++i)
        threads.push_back(std::thread(count1m, i));
    ready = true;

    for (auto & th:threads)
        th.join();

    return 0;
}

 

这个程序的输出只会有一个线程输出。

但是如果不使用原子锁则会有多个线程胜出。

bool bwin = false;

void count1m(int id)
{
    while (!ready) {
        std::this_thread::yield();
    } // 等待主线程中设置 ready 为 true.

    //if (!winner.test_and_set()) 
    if (!bwin)
    {
        bwin = true;
        std::cout << "thread #" << id << " won!\n";
    }
};

 

如果将线程函数改成普通变量,则输出可能是这样:

 

atomic<T> 模板类

上述的atomic_flag只是对于bool类型的变量进行原子操作;通过atomic模板类可以对更多的类型进行原子操作。

atomic类本身是禁止复制和拷贝的,但其模板的对象可以直接赋值给atomic类,相当于隐式转换。

eg:

    atomic<int> aint1;
    aint1 = 1;

    atomic<int> aint2(2);
    // atomic<int> aint2(aint1); //错误:禁止拷贝构造
    // aint2 = aint1; //错误:禁止赋值

 

 

is_lock_free() :atomic并不保证模板类是无锁的,所以可以通过这个接口判断是否需要加锁。atomic_flag一定是无锁的。

  至于判断无锁的标准可能跟模板类的大小和硬件操作系统有关。

  可参考:https://blog.csdn.net/weixin_43705457/article/details/103979660

store() : 更新原子变量模板对象

  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;
 
load() : 返回原子变量模板对象
  T load (memory_order sync = memory_order_seq_cst) const volatile noexcept;
  T load (memory_order sync = memory_order_seq_cst) const noexcept;
 
exchange() : 更新原子变量模板对象,并返回旧的对象,类似于atomic_flag的test_and_set()接口,此操作也是原子的。
  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()
bool compare_exchange_weak (T& expected, T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
bool compare_exchange_weak (T& expected, T val, memory_order sync = memory_order_seq_cst) noexcept;
用于比较expected的值于atomic内部的值是否相等:
   相等:则将val的值赋值给内部的模板对象
   不相等:则内部的值赋值给expected

atomic针对整形及指针的优化

提供一些算数运算的操作符,且都是原子操作的。

 

posted @ 2021-04-30 16:28  Dylan_Liang  阅读(926)  评论(0编辑  收藏  举报