zoukankan      html  css  js  c++  java
  • 【C++11 多线程】原子操作(六)

    一、C++11为什么要引入原子操作?

    首先为什么会有原子操作呢?这纯粹就是 C++ 这门语言的特性所决定的,C++ 这门语言是为性能而生的,它对性能的追求是没有极限的,它总是想尽一切办法提高性能。互斥锁是可以实现数据的同步,但同时是以牺牲性能为代价的。口说无凭,我们做个实验就知道了。

    我们将一个数加一再减一,循环一定的次数,开启 20 个线程来观察,这个正确的结果应该是等于 0 的。


    1.1 示例一:不加任何互斥锁同步

    #include <iostream>
    #include <thread>
    #include <atomic>
    #include <time.h>
    #include <mutex>
    
    #define MAX 100000
    #define THREAD_COUNT 20
    
    int total = 0;
    
    void threadTask()
    {
        for (int i = 0; i < MAX; i++)
        {
            total += 1;
            total -= 1;
        }
    }
    
    int main()
    {
        clock_t start = clock();
        std::thread t[THREAD_COUNT];
    
        for (int i = 0; i < THREAD_COUNT; ++i)
        {
            t[i] = std::thread(threadTask);
        }
        for (int i = 0; i < THREAD_COUNT; ++i)
        {
            t[i].join();
        }
    
        clock_t finish = clock();
        std::cout << "result:" << total << std::endl; // 计算结果
        std::cout << "duration:" << finish - start << "ms" << std::endl; // 计算耗时
    
        return 0;
    }
    

    输出如下:

    result:8
    duration:13ms
    

    可以看出程序运行耗时很短,只要 13ms,但是结果却是不正确的。


    1.2 示例二:线程加上互斥锁mutex

    #include <iostream>
    #include <thread>
    #include <atomic>
    #include <time.h>
    #include <mutex>
    
    #define MAX 100000
    #define THREAD_COUNT 20
    
    int total = 0;
    std::mutex g_mutex; // 新实例一个互斥锁
    
    void threadTask()
    {
        for (int i = 0; i < MAX; i++)
        {
            g_mutex.lock(); // 加锁
            total += 1;
            total -= 1;
            g_mutex.unlock(); // 解锁
        }
    }
    
    int main()
    {
        clock_t start = clock();
        std::thread t[THREAD_COUNT];
    
        for (int i = 0; i < THREAD_COUNT; ++i)
        {
            t[i] = std::thread(threadTask);
        }
        for (int i = 0; i < THREAD_COUNT; ++i)
        {
            t[i].join();
        }
    
        clock_t finish = clock();
        std::cout << "result:" << total << std::endl; // 计算结果
        std::cout << "duration:" << finish - start << "ms" << std::endl; // 计算耗时
    
        return 0;
    }
    

    输出如下:

    result:0
    duration:357ms
    

    可以看到运行结果是正确的,但是耗时比原来长多了。虽然很无奈,但这也是没有办法的,因为只有在保证准确的前提才能去追求性能。


    1.3 示例三:原子操作

    定义原子操作的时候必须引入头文件:#include <atomic>

    #include <iostream>
    #include <thread>
    #include <atomic>
    #include <time.h>
    #include <mutex>
    #include <atomic>
    
    #define MAX 100000
    #define THREAD_COUNT 20
    
    // 原子操作
    std::atomic_int total(0);
    
    void threadTask()
    {
        for (int i = 0; i < MAX; i++)
        {
            total += 1;
            total -= 1;
        }
    }
    
    int main()
    {
        clock_t start = clock();
        std::thread t[THREAD_COUNT];
    
        for (int i = 0; i < THREAD_COUNT; ++i)
        {
            t[i] = std::thread(threadTask);
        }
        for (int i = 0; i < THREAD_COUNT; ++i)
        {
            t[i].join();
        }
    
        clock_t finish = clock();
        std::cout << "result:" << total << std::endl; // 计算结果
        std::cout << "duration:" << finish - start << "ms" << std::endl; // 计算耗时
    
        return 0;
    }
    

    输出如下:

    result:0
    duration:170ms
    

    可以看到,我们在这里只需要定义atomic_int total(0)就可以实现原子操作了,就不需要互斥锁了。而性能的提升也是比较明显的,这就是原子操作的魅力所在。

    虽然已经展示了一个原子操作的示例,但还是在后面继续详细说明下原子操作。

    二、原子操作

    所谓的原子操作,取的就是“原子是最小的、不可分割的最小个体”的意义,它表示在多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源。也就是他确保了在同一时刻只有唯一的线程对这个资源进行访问。这有点类似互斥对象对共享资源的访问的保护,但是原子操作更加接近底层,因而效率更高。

    在以往的 C++ 标准中并没有对原子操作进行规定,我们往往是使用汇编语言,或者是借助第三方的线程库,例如 intel 的 pthread 来实现。C++11 引入了原子操作的概念,并通过这个新的头文件<atomic>提供了多种原子操作数据类型,如下图所示:

    img


    std::atomic<>对象提供了常见的原子操作(通过调用成员函数实现对数据的原子操作): store 是原子写操作,load 是原子读操作,exchange 是对两个数值进行交换的原子操作。

    即使使用了std::atomic<>,也要注意执行的操作是否支持原子性,也就是说,你不要觉得用的是具有原子性的变量(准确说是对象)就可以为所欲为了,你对它进行的运算不支持原子性的话,也不能实现其原子效果。一般针对 ++,–,+=,-=,&=,|=,^= 是支持的,这些原子操作是通过在std::atomic<>对象内部进行运算符重载实现的。

    原子操作一般用于用户统计。例如统计一共发送了多少数据包,接收了多少个数据包。


    参考:

    C++11多线程编程(四)——原子操作


  • 相关阅读:
    nodejs gulp如何获取参数
    nodejs 获取当前路径的方法
    gulp 如何排除文件和文件夹
    小技巧css解决移动端ios不兼容position:fixed属性,无需插件
    代码协议相关
    zen-coding
    directive 实例讲解
    angular api 地址
    一步一步搭建hibernate4+ spring+ struts2
    华为荣耀6 H60-L02/L12(联通版)救砖包【适用于无限重启】
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/14577248.html
Copyright © 2011-2022 走看看