C++ 多线程编程(四) 原子类型atomic

文章介绍了C++11引入的atomic模板类,用于实现无锁编程,提高多线程环境下的效率。通过对比使用mutex和atomic的多线程自增操作,展示了atomic的高效性。同时,列举了atomic的基本用法,包括构造函数、store和load函数,以及fetch_*系列函数,强调了is_lock_free方法在确定操作是否免锁方面的作用。
摘要由CSDN通过智能技术生成

C++ 11增加了原子类型atomic类,在一定条件下可以实现无锁编程。

1. 简介

atomic是一个模板类,定义如下:

template< class T >  struct atomic;

atomic可以实现无锁编程,在效率上要比mutex高很多,直接看个直观的例子:

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

int sum1{0};
std::atomic<int> sum2{0};
std::mutex mtx;
const int max_ = 100000000;

void add1()
{
    while (1) {
        mtx.lock();
        if (sum1 >= max_)
        {
            mtx.unlock();
            return;
        }
        sum1++;
        mtx.unlock();
    }
}

void add2()
{
    while (1)
    {
        if (sum2 >= max_) return;
        sum2++;
    }
}

int main()
{
    {
        std::vector<std::thread> ths;
        std::cout << "begin, sum1=" << sum1 << std::endl;
        auto start = std::chrono::system_clock::now();
        for (int i = 0; i <= 10; i++)
        {
            std::thread th{add1};
            ths.push_back(std::move(th));
        }
        for (int i = 0; i <= 10; i++)
        {
            ths[i].join();
        }
        auto finish = std::chrono::system_clock::now();
        auto cost = std::chrono::duration_cast<std::chrono::milliseconds>(finish-start);
        std::cout << "completed, sum1=" << sum1 << std::endl;
        std::cout << "mutex cost: " << (double)cost.count() << "ms" << std::endl;
    }
    std::cout << std::endl;
    {
        std::vector<std::thread> ths;
        std::cout << "begin, sum2=" << sum2 << std::endl;
        auto start = std::chrono::system_clock::now();
        for (int i = 0; i <= 10; i++)
        {
            std::thread th{add2};
            ths.push_back(std::move(th));
        }
        for (int i = 0; i <= 10; i++)
        {
            ths[i].join();
        }
        auto finish = std::chrono::system_clock::now();
        auto cost = std::chrono::duration_cast<std::chrono::milliseconds>(finish-start);
        std::cout << "completed, sum2=" << sum2 << std::endl;
        std::cout << "atomic cost: " << (double)cost.count() << "ms" << std::endl;
    }
}

这个例子里边,用多线程对一个变量自增,自增到一个值后结束,比较加锁和原子类型的运行时间,结果如下:

经多次测试,这个示例中atomic的效率是mutex的3~4倍。

2. 接口用法

下面记录的是atomic类的常用接口,全部接口在这里看

2.1 构造函数

atomic() noexcept = default;

constexpr atomic(T desired) noexcept;  //常用

atomic(const atomic&) = delete;  //禁用拷贝构造函数

 示例:

std::atomic<int> sum1{0};
std::atomic<int> sum2(1);
std::atomic<int> sum3(sum2);  //error, 禁止拷贝

2.2 修改、访问

atomic对象的修改主要使用store函数,访问主要使用load函数。

void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;

T load(std::memory_order order = std::memory_order_seq_cst) const noexcept;

std::memory_order是内存顺序,太复杂了,摆烂了,默认值又不是不能用!这篇勉强能看懂...

#include <atomic>
#include <iostream>

int main()
{
    std::atomic<int> a{1};
    std::cout << a.load() << std::endl;
    a.store(4);
    std::cout << a.load() << std::endl;
}
输出:
1
4

2.3 fetch_* 系列函数

fetch_ 开头的函数一共有5个,功能是简单的加减与之类的运算。与fetch_ 系列函数对应的运算符同样是5个,他们的功能是一样的。

fetch_*对应运算符功能
fetch_add+=原子地将参数加到存储于原子对象的值,并返回先前保有的值
fetch_sub-=原子地从存储于原子对象的值减去参数,并获得先前保有的值
fetch_and&=原子地进行参数和原子对象的值的逐位与,并获得先前保有的值
fetch_or|=原子地进行参数和原子对象的值的逐位或,并获得先前保有的值
fetch_xor^=原子地进行参数和原子对象的值的逐位异或,并获得先前保有的值
#include <atomic>
#include <iostream>

int main()
{
    std::atomic<int> a{1};
    std::cout << a.load() << std::endl;
    a.fetch_add(5);
    std::cout << a.load() << std::endl;
}
输出:
1
6

2.4 其他接口

2.4.1  is_lock_free

bool is_lock_free() const noexcept;

检查此类型所有对象上的原子操作是否免锁。

如果免锁,返回true,如果不是,会编译不过,这里是挺奇怪的。经过测试,模板类型的大小为1、2、4、8字节时,atomic是免锁的,其他情况都会编译不过,详情

2.4.2  exchange

T exchange( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept;

原子地以 desired 替换底层值。操作为读-修改-写操作。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值