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

本文介绍 <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 = ; i < ; ++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 = ; i <= ; ++i)
threads.push_back(std::thread(count1m, i));
ready = true; for (auto & th:threads)
th.join(); return ;
}

多次执行结果如下:

atomic ) ./Atomic-Flag1
spawning threads that count to million...
thread # won!
atomic ) ./Atomic-Flag1
spawning threads that count to million...
thread # won!
atomic ) ./Atomic-Flag1
spawning threads that count to million...
thread # won!
atomic ) ./Atomic-Flag1
spawning threads that count to million...
thread # won!
atomic ) ./Atomic-Flag1
spawning threads that count to million...
thread # won!
atomic ) ./Atomic-Flag1
spawning threads that count to million...
thread # 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 = ; i <= ; ++i)
threads.push_back(std::thread(append_number, i));
for (auto & th:threads)
th.join(); std::cout << stream.str() << std::endl;;
return ;
}

执行结果如下:

thread #
thread #
thread #
thread #
thread #
thread #
thread #
thread #
thread #
thread #

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 = ; cnt < ; ++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 = ; n < ; ++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 对象可以当作一个简单的自旋锁使用。

C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)的更多相关文章

  1. C++11 并发指南六(atomic 类型详解四 C 风格原子操作介绍)

    前面三篇文章<C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)>.<C++11 并发指南六( <atomic> 类型详解二 std::at ...

  2. C++11 并发指南六( <atomic> 类型详解二 std::atomic )

    C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)  一文介绍了 C++11 中最简单的原子类型 std::atomic_flag,但是 std::atomic_flag ...

  3. C++11 并发指南六(atomic 类型详解三 std::atomic (续))

    C++11 并发指南六( <atomic> 类型详解二 std::atomic ) 介绍了基本的原子类型 std::atomic 的用法,本节我会给大家介绍C++11 标准库中的 std: ...

  4. 【C/C++开发】C++11 并发指南三(std::mutex 详解)

    本系列文章主要介绍 C++11 并发编程,计划分为 9 章介绍 C++11 的并发和多线程编程,分别如下: C++11 并发指南一(C++11 多线程初探)(本章计划 1-2 篇,已完成 1 篇) C ...

  5. C++11 并发指南五(std::condition_variable 详解)

    前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread,std::mut ...

  6. C++11 并发指南五(std::condition_variable 详解)(转)

    前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread,std::mut ...

  7. 【转】C++11 并发指南五(std::condition_variable 详解)

    http://www.cnblogs.com/haippy/p/3252041.html 前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三 ...

  8. C++11 并发指南三(std::mutex 详解)

    上一篇<C++11 并发指南二(std::thread 详解)>中主要讲到了 std::thread 的一些用法,并给出了两个小例子,本文将介绍 std::mutex 的用法. Mutex ...

  9. C++11 并发指南三(std::mutex 详解)(转)

    转自:http://www.cnblogs.com/haippy/p/3237213.html 上一篇<C++11 并发指南二(std::thread 详解)>中主要讲到了 std::th ...

随机推荐

  1. GIM企业即时通讯

    GIM企业即时通讯是笔者Garfield(QQ:3674571)采用.NetFramework4.0+SQL2008R2开发的一套企业内网/外网 通用的即时通讯(IM)软件,分为服务器端和客户端,通讯 ...

  2. Excel的文件打开特别慢,xls文件特别大解决一例

    Excel的文件打开特别慢,xls文件特别大解决一例 打开Excel的xls文件打开特别慢,而且操作也非常慢,动辄需要10几20分钟,很不正常.一个简单的Excel的xls文件有10几兆,甚至几百兆的 ...

  3. MySQL相关操作知识

    1.解决客户端联不上MySQL服务器的问题: GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '' WITH GRANT OPTION; ...

  4. VC++ 如何在显示对话框的时候,指定焦点控件!

    很简单: 在你的CAddDlg类的OnInitDialog函数中加上你上面的代码GetDlgItem(IDC_EDIT1)->SetFocus();最后记得return FALSE; 其实,不知 ...

  5. input输入子系统

    一.什么是input输入子系统? 1.Linux系统支持的输入设备繁多,例如键盘.鼠标.触摸屏.手柄或者是一些输入设备像体感输入等等,Linux系统是如何管理如此之多的不同类型.不同原理.不同的输入信 ...

  6. unity3d 射弹基础案例

    小白本来想学cocos2dx的,然而c++难学就算了,cocos2dx对新手来说简直坑爹,于是乎转战unity3d学习js,在写出第一个游戏后兴致高多了哎. 回顾一下编辑的过程:1.建立一个cube作 ...

  7. 获取 input 单选框和多选框的值

    引用  jQuery的js <script> $(function(){ var arr = new Array(); $('#checkbox').click(function(){ a ...

  8. Spring —— 三种配置数据源的方式:spring内置、c3p0、dbcp

    01.Spring内置数据源配置Class:DriverManagerDataSource全限定名:org.springframework.jdbc.datasource.DriverManagerD ...

  9. 用Python玩转词云

    第一步:引入相关的库包: #coding:utf-8 __author__ = 'Administrator' import jieba #分词包 import numpy #numpy计算包 imp ...

  10. flume远程调试

    项目开发的时候,出现问题的时候,通常在IDE里面直接进行调试,但有时候我们可能用的是另外的一些开源框架,甚至运行程序里面没有一行代码是我们自己写的,如果出现一些较复杂的问题,那么我们可能就会用到远程调 ...