C++ 多线程编程时的数据一直性,以及volatile、atomic、mutex的使用选择

volatile

#include <iostream>
#include <thread>

volatile int total{ 0 };

void func(int) {
    for (int i = 0; i < 10000; ++i) {
        total += i;
    }
}

int main() {
    std::thread t1(func, 0);
    std::thread t2(func, 0);

    t1.join();
    t2.join();

    std::cout << "total: " << total << std::endl;
    return 0;
}

声明某个变量的值是随时可能被改变的,每次读取次变量都从内存地址中直接读取。
为了防止编译器的优化而从寄存器中读取数据,而导致多线程时数据不一致。
但是volatile仅仅是针对编译器的,对CPU无影响,因此再多核环境下没有任何作用。

  1. 与平台无关的多线程程序,volatile几乎无用(JAVA的volatile除外,java的volatile有内存屏障指令)
  2. volatile不保证原子性(一般需使用CPU提供的LOCK指令)
  3. volatile不保证执行顺序
  4. volatile不提供内存屏障和内存栅栏
  5. 多核环境中内存的可见性和CPU执行顺序不能通过volatile来保障,而是以来于CPU的内存屏障

atomic

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

std::atomic_int total{ 0 };
// 或 std::atomic<int> total{ 0 };

void func(int) {
    for (int i = 0; i < 10000; ++i) {
        total += i;
    }
}

int main() {
    std::thread t1(func, 0);
    std::thread t2(func, 0);

    t1.join();
    t2.join();

    std::cout << "total: " << total << std::endl;
    return 0;
}

C++11中引入了atomic,使得程序相对mutex更加简洁
编译器或处理器可能会改变代码的执行顺序。
默认情况下C++11中的原子类型的变量在线程中总是保持着顺序执行的特性(memory_order默认参数memory_order_seq_cst 全部存取都按照顺序执行,非原子类型没有必要,因为不需要在线程间同步)。我们称这样的特性为“顺序一致”。
x86、SPARC(TSO模式)是强顺序内存模型平台。
PowerPC、ArmV7 是弱内存模型构架,如果要保证指令执行的顺序,通常需要有再汇编指令中加入一条所谓的内存栅栏(memory barrier)指令。如在PowerPC上,就有一条名为sync的内存栅栏指令,其对高度流水化的PowerPC处理器的性能影响很大。
如果在强顺序内存模型平台(如x86)上,没必要指定memory_order参数
如果在弱顺序内存模型平台(如PowerPC)上,可以手动指定memory_order参数来提高性能

mutex

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

std::atomic<int> total{ 0 };

void func(int) {
    for (int i = 0; i < 10000; ++i) {
        int temp = total;
        temp += i;
        total = temp;
    }
}

int main() {
    std::thread t1(func, 0);
    std::thread t2(func, 0);

    t1.join();
    t2.join();

    std::cout << "total: " << total << std::endl;
    return 0;
}

再上述的代码中往往达不到我们想要的效果,因为atomic只能保证 int temp = total; 和 total = temp; 的原子性,但是如果再中间做了一些其他事,是不能保证原子性的,这个时候应该采用mutex

#include <iostream>
#include <thread>
#include <mutex>

int total{ 0 };
std::mutex total_mutex;

void func(int) {
    for (int i = 0; i < 10000; ++i) {
        std::lock_guard<std::mutex> lock(total_mutex);
        int temp = total;
        temp += i;
        total = temp;
    }
}

int main() {
    std::thread t1(func, 0);
    std::thread t2(func, 0);

    t1.join();
    t2.join();

    std::cout << "total: " << total << std::endl;
    return 0;
}

参考文章:《深入理解C++11》
https://www.cnblogs.com/tekkaman/p/10245341.html
https://blog.csdn.net/D_Guco/article/details/74826041?utm_source=blogxgwz5

posted @ 2020-04-17 15:19  ghx_kevin  阅读(1444)  评论(0编辑  收藏  举报