C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)

C++11 并发指南已经写了 5 章,前五章重点介绍了多线程编程方面的内容,但大部份内容只涉及多线程、互斥量、条件变量和异步编程相关的 API,C++11 程序员彻底能够没必要知道这些 API 在底层是如何实现的,只须要清楚 C++11 多线程和异步编程相关 API 的语义,而后熟加练习便可应付大部分多线程编码需求。可是在不少极端的场合下为了性能和效率,咱们须要开发一些 lock-free 的算法和数据结构,前面几章的内容可能就派不上用场了,所以从本文开始介绍 C++11 标准中 <atomic> 头文件里面的类和相关函数。ios

本文介绍 <atomic> 头文件中最简单的原子类型: atomic_flag。atomic_flag 一种简单的原子布尔类型,只支持两种操做,test-and-set 和 clear。程序员

std::atomic_flag 构造函数

std::atomic_flag 构造函数以下:算法

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

std::atomic_flag 只有默认构造函数,拷贝构造函数已被禁用,所以不能从其余的 std::atomic_flag 对象构造一个新的 std::atomic_flag 对象。编程

若是在初始化时没有明确使用 ATOMIC_FLAG_INIT初始化,那么新建立的 std::atomic_flag 对象的状态是未指定的(unspecified)(既没有被 set 也没有被 clear。)另外,atomic_flag不能被拷贝,也不能 move 赋值。数据结构

ATOMIC_FLAG_INIT: 若是某个 std::atomic_flag 对象使用该宏初始化,那么能够保证该 std::atomic_flag 对象在建立时处于 clear 状态。多线程

下面先看一个简单的例子,main() 函数中建立了 10 个线程进行计数,率先完成计数任务的线程输出本身的 ID,后续完成计数任务的线程不会输出自身 ID:并发

#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.

    for (int i = 0; i < 1000000; ++i) {
    } // 计数.

    // 若是某个线程率先执行完上面的计数过程,则输出本身的 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;
    std::cout << "spawning 10 threads that count to 1 million...\n";
    for (int i = 1; i <= 10; ++i)
        threads.push_back(std::thread(count1m, i));
    ready = true;

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

    return 0;
}

屡次执行结果以下:app

atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #6 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #1 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #5 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #1 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #1 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #10 won!

std::atomic_flag::test_and_set 介绍

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;

test_and_set() 函数检查 std::atomic_flag 标志,若是 std::atomic_flag 以前没有被设置过,则设置 std::atomic_flag 的标志,并返回先前该 std::atomic_flag 对象是否被设置过,若是以前 std::atomic_flag 对象已被设置,则返回 true,不然返回 false。异步编程

test-and-set 操做是原子的(所以 test-and-set 是原子 read-modify-write (RMW)操做)。

test_and_set 能够指定 Memory Order(后续的文章会详细介绍 C++11 的 Memory Order,此处为了完整性列出 test_and_set 参数 sync 的取值),取值以下:

 

Memory Order 值 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>                // std::cout
#include <atomic>                // std::atomic_flag
#include <thread>                // std::thread
#include <vector>                // std::vector
#include <sstream>                // std::stringstream

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 > threads;
    for (int i = 1; i <= 10; ++i)
        threads.push_back(std::thread(append_number, i));
    for (auto & th:threads)
        th.join();

    std::cout << stream.str() << std::endl;;
    return 0;
}

执行结果以下:

thread #1
thread #2
thread #3
thread #4
thread #5
thread #6
thread #7
thread #8
thread #9
thread #10

std::atomic_flag::clear() 介绍

清除 std::atomic_flag 对象的标志位,即设置 atomic_flag 的值为 false。clear 函数原型以下:

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

清除 std::atomic_flag 标志使得下一次调用 std::atomic_flag::test_and_set 返回 false。

std::atomic_flag::clear() 能够指定 Memory Order(后续的文章会详细介绍 C++11 的 Memory Order,此处为了完整性列出 clear 参数 sync 的取值),取值以下:

 

Memory Order 值 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

结合 std::atomic_flag::test_and_set() 和 std::atomic_flag::clear(),std::atomic_flag 对象能够看成一个简单的自旋锁使用,请看下例:

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

std::atomic_flag lock = ATOMIC_FLAG_INIT;

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

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::atomic_flag 对象 lock 的上锁操做能够理解为 lock.test_and_set(std::memory_order_acquire); (此处指定了 Memory Order,更多有关 Memory Order 的概念,我会在后续的文章中介绍),解锁操做至关与 lock.clear(std::memory_order_release)。

在上锁的时候,若是 lock.test_and_set 返回 false,则表示上锁成功(此时 while 不会进入自旋状态),由于此前 lock 的标志位为 false(即没有线程对 lock 进行上锁操做),但调用 test_and_set 后 lock 的标志位为 true,说明某一线程已经成功得到了 lock 锁。

若是在该线程解锁(即调用 lock.clear(std::memory_order_release)) 以前,另一个线程也调用 lock.test_and_set(std::memory_order_acquire) 试图得到锁,则 test_and_set(std::memory_order_acquire) 返回 true,则 while 进入自旋状态。若是得到锁的线程解锁(即调用了 lock.clear(std::memory_order_release))以后,某个线程试图调用 lock.test_and_set(std::memory_order_acquire) 而且返回 false,则 while 不会进入自旋,此时代表该线程成功地得到了锁。

按照上面的分析,咱们知道在某种状况下 std::atomic_flag 对象能够看成一个简单的自旋锁使用。

相关文章
相关标签/搜索