> For the complete documentation index, see [llms.txt](https://blog.owent.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://blog.owent.net/2012/611.md).

# C++ 新特性学习（八） — 原子操作和多线程库\[多工内存模型]

这是我对C++新特性系统学习的最后一部分，之后就靠实践中再来看新标准的新特性啦。

```
在之前，我对这部分没太在意，直到看到了一篇文章 [http://blog.csdn.net/pongba/article/details/1659952](http://blog.csdn.net/pongba/article/details/1659952) 才意识到，C++的多线程操作也是个麻烦的问题。

简而言之，C++编译器在进行编译优化的时候，认为当前是单进程的，并且遵循**可观察行为**（Observable Behavior）不变的原则。就是说在可观察行为不变的情况下，操作是可以被改变顺序的，而单进程可观察行为不变，不代表在多进程的情况下仍然不变。还是上大牛的例子：

_**例子一：**_
```

| x = y = 0;                    |                               |
| ----------------------------- | ----------------------------- |
| 线程1                           | 线程2                           |
| <p>if(x == 1)<br>    ++y;</p> | <p>if(y == 1)<br>    ++x;</p> |

完全可以优化成

| x = y = 0;                             |                                        |
| -------------------------------------- | -------------------------------------- |
| 线程1                                    | 线程2                                    |
| <p>++y;<br> if(x != 1)<br>    --y;</p> | <p>++x;<br> if(y != 1)<br>    --x;</p> |

分别对于两个进程而言，可观察行为确实没有变化。而这种优化在某些时候确实会有比较明显的效果。但是很显然，语义变化了。在原来的结果里不可能发生 x和y都为0的情况，而优化过后，有可能出现。 **再来个例子：**

```cpp
for (...) {
    ...
    if (mt)
        pthread_mutex_lock(...);
    x = ... x ...
    if (mt)
        pthread_mutex_unlock(...);
}
// 当它被Register Promotion华丽丽地优化成
r = x;
for (...) {
    ...
    if (mt) {
        x = r;
        pthread_mutex_lock(...);
        r = x;
    }
    r = ... r ...
    if (mt) {
        x = r;
        pthread_mutex_unlock(...);
        r = x;
    }
}
x = r;
```

做何感想？所以说，现在的多线程库多少都是有缺陷的，要解决这一问题，只能从语言内存模型上动手脚了。

这里主要介绍两个库，原子操作和线程库 原子操作（Atomic） 头文件 #include  原子操作只支持C++类型 基本类型 std::atomic 扩展实现 std::atomic\_char, std::atomic\_int, std::atomic\_uint 等是stl中的默认实现。 这个类型用于对数据进行原子操作，在操作的过程中可以指定内存规则。 主要的函数如下：

| 函数名                                             | 描述                       |
| ----------------------------------------------- | ------------------------ |
| atomic\_store                                   | 保存非原子数据到原子数据结构           |
| atomic\_load                                    | 读取原子结构中的数据               |
| atomic\_exchange                                | 保存非原子数据到原子数据结构，返回原来保存的数据 |
| atomic\_fetch\_add                              | 对原子结构中的数据做加操作            |
| atomic\_fetch\_sub/atomic\_fetch\_sub\_explicit | 对原子结构中的数据做减操作            |
| atomic\_fetch\_and                              | 对原子结构中的数据逻辑与             |
| atomic\_fetch\_or                               | 对原子结构中的数据逻辑或             |
| atomic\_fetch\_xor                              | 对原子结构中的数据逻辑异或            |

刚才提到了在原子操作时候的内存操作规则，内存操作规则主要是 std::memory\_order，这是个枚举类型，里面包含着N多规则

| 值                       | 定义规则                                                                                                            |
| ----------------------- | --------------------------------------------------------------------------------------------------------------- |
| memory\_order\_relaxed  | 不保证顺序                                                                                                           |
| memory\_order\_consume  | 类比生产者-消费者模型中的消费者读取动作（仅是读取，无计数器），保证该操作先于依赖于当前读取的数据（比如后面用到了这次读取的数据）不会被提前，但不保证其他读取操 作的顺序。仅对大多编译环境的多线程程序的编译优化过程有影响。 |
| memory\_order\_acquire  | 类比生产者-消费者模型中的消费者读取动作（仅是读取，无计数器），保证在这个操作之后的所有操作不会被提前，同样对大多编译环境的多线程程序的编译优化过程有影响。                                  |
| memory\_order\_release  | 类比生产者-消费者模型中的生产者创建动作（仅操作一个数据），保证这之前的操作不会被延后。                                                                    |
| memory\_order\_acq\_rel | 同时包含memory\_order\_acquire和memory\_order\_release标记                                                             |
| memory\_order\_seq\_cst | 全部存取都按顺序执行，在多核系统上容易成为性能瓶颈                                                                                       |

在前面的原子操作的函数中，默认规则都是std::memory\_order\_seq\_cst 此外，atomic还有一些标记类型和测试操作，比较类似操作系统里的原子操作

* std::atomic\_flag : 标记类型
* atomic\_flag\_test\_and\_set : 尝试设置为占用（原子操作）
* atomic\_flag\_clear : 释放（原子操作）

```cpp
std::atomic_flag lock = ATOMIC_FLAG_INIT;

void f(int n) {
    for(int cnt = 0; cnt < 100; ++cnt) {
        while(std::atomic_flag_test_and_set_explicit(&lock, std::memory_order_acquire));
        std::cout << "线程 " << n << std::endl;
        std::atomic_flag_clear_explicit(&lock, std::memory_order_release);
    }
}

int main() {
    std::atomic_int a;

    a.store(100);
    a.fetch_add(105);

    int i = a.load(std::memory_order_consume);

    printf("i => %d\n", i);

    // 原子标记
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n) {
        v.emplace_back(f, n);
    }
    for (auto t = v.begin(); t != v.end(); ++ t) {
        t->join();
    }

    return 0;
}
```

## 多线程库

前面的代码里已经用到了一点多线程库的东西了 包含文件： #include  涉及的类是 std::thread 构造函数里有两种

* 一种是传入函数指针和参数（VC里参数最多四个）。线程立即执行
* 另一种是空构造。不会新增线程

另外**需要注意**的是std::thread的等于操作符是必须要通过move语义的，并且如果joinable的话会执行std::terminate()。 函数茫茫多，建议用的时候看文档去，我这里就不废话了，上面已经用到一点了（PS：配std::bind是相当的华丽哦） 文档如这行 <http://en.cppreference.com/w/cpp/thread> 以前写过一些测试代码，后来扔掉了。反正也很简单…

路漫漫其修远兮，吾将写代码而求索.

附上对C++11一些特性研究的总结：
