• 《C++ concurrency in action》 读书笔记 -- Part 4 第五章 C++的多线程内存模型 (1)


    《C++ concurreny in action》 第五章 C++的内存模型和原子操作

    5.1 Memory model basics (内在模型基础)

    Memory model 涉及两个方面:structural 和 concurrency

    structural 是基础,主要是对象的布局

    5.1.1 Objects and memory location

    The C++ Standard defines an object as “a region of storage,”

    clip_image001

    注意四点:

    • 所有变量都有object,包括成员变量
    • 所有object都有自己的内存位置
    • 基础类型(int之类的)都有自己单独的内存区
    • bit field 共享同一个内存区

    5.1.2 Objects, memory locations, and concurrency

    产生race condition的条件就是多个线程访问同一个memory location,同时至少有一个在修改这个memory location的值。

    必须要控制访问顺序来避免race condition。两种方法:1. 锁(mutex)2. 原子操作(atomic operation)

    5.1.3 Modification orders

    数据的修改顺序必须也有限制,否则会产生data race

    5.2 Atomic operations and types in C++ (C++中的原子操作和类型)

    An atomic operationis an indivisible operation.

    原子操作就是不可分割的操作。要不就完成了, 要不就还没有做。不可能出现“只做了一半”的状态

    在C++中,我们可以通过原子类型(atomic type)来进行原子操作。

    5.2.1 Tthe standard atomic types

    标准的原子类型都在头文件<atomic>中。这里头的类型的操作都是原子操作。

    大多数都有 is_lock_free() 这个成员函数,如果返回 true ,则这个是“真正的原子操作(用的真正的原子操作指令)”,返回 false,则是使用锁来模拟。

    只有std::atomic_flag不带有is_lock_free这个函数。因为这个类型必须是真正的原子操作。

    其它的原子类型都是以std::atomic<>来实现的。

    clip_image002

    标准库中的原子类型都是不可拷贝和赋值的(not copyable or assignable)

    原子类型的操作函数中都有一个memory-ordering的参数选项,可以精确控制 memory-ordering 语义。但这相关的主要在5.3节详述。

    原子类型的操作分三类:

    • (存储操作)Store operations, 有这几个函数: memory_order_relaxed, memory_order_release, or memory_order_seq_cstordering
    • (Load操作?)Load operations,有这几个函数: memory_order_relaxed, memory_order_consume, memory_order_acquire,or memory_order_seq_cstordering
    • (修改操作)Read-modify-write operations, 有这几个函数:memory_order_relaxed, memory_

    order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel,or memory_order_seq_cstordering

    5.2.2 Operations on std::atomic_flag

    std::atomic_flag 是标准库中最简单的原子类型,代表一个 bool 标志。这个类型的对象分两种状态:set 或者是 clear。这个类型的设计目的就是作为构建其它的原子类型,因此很少见它会被普通的程序使用。但因为很有代表性,所以本书从这个类型开始讲起。

    std::atomic_flag的对象必须用 ATOMIC_FLAG_INIT进行初始化,初始化后对象会处于clear状态。(它是唯一一个对初始化有特殊要求的原子类型,但同时也是唯一一个会被保证是lock_free实现的原子类型)。静态类型的std::atomic_flag也会由编译器来保证初始化。

    std::atomic_flag f=ATOMIC_FLAG_INIT;

    一个被初始化后的std::atomic_flag对象可以做的三种操作有:

    • destroy(通过析构函数)
    • clear(通过 clear()函数)  store操作  参数可以指定memory-ordering tags,但是不能使用 memory_order_acquire or memory_order_acq_rel 这两种语义
    • set并读取状态(通过test_and_set()函数)read_modify_write操作,可以使用任何memory-ordering tags。
    f.clear(std::memory_order_release); 
    bool x=f.test_and_set();

    标准库的原子类型的操作都是原子操作的,标准库的原子类型都不带拷贝和赋值,因为拷贝和赋值不可能是“原子操作”(涉及两个对象)。

    因为本身只有有限几个关键的操作,所以std::atomic_flag很适合用在实现自旋锁。

    class spinlock_mutex
    {
        std::atomic_flag flag;
    public:
        spinlock_mutex():flag(ATOMIC_FLAG_INIT)
        { }
        void lock()
        {
            while(flag.test_and_set(std::memory_order_acquire));
        }
        void unlock()
        {
            flag.clear(std::memory_order_release);
        }
    };

    这个实现非常简陋,但已经可以足够用在std::lock_guard<>上,作为互斥锁来使用了。

    std::atomic_flag的操作实在太有限,因此无法作为一个通用的bool标志来使用(因为没有一个单纯做值读取的操作)。如果需要通用的bool标志,那么应该使用 std::atomic<bool>。

    5.2.3 Operations on std::atomic<bool>

    std::atomic<boo>可以通过一个bool值来构建

    std::atomic<bool> b(true);
    b=false;

    std::atomic类型的赋值都是(非atomic类型的)返回值而不是引用。比如(std::atomic<int>的赋值操作返回的是int而不是int&),以避免在别的线程获取这个引用并通过非原子操作来修改它。

    与std::atomic_flag不同,std::atomic<boo>通过以下几个方法来操作:

    • store()  => 赋值,可以指定memory_older
    • load() => 取得原子类型对象的值
    • exchange() => read_modify_write操作。

    下面是代表示例:

    std::atomic<bool> b;
    bool x=b.load(std::memory_order_acquire);
    b.store(true);
    x=b.exchange(false,std::memory_order_acq_rel);

    std::atomic<boo>还有一些别的read_modify_write操作:

    compare_exchange_weak( T& expected, T desired, ……)和compare_exchange_strong(T& expected, T desired, ……)

    这两个函数我们现在只关注前两个参数(所以后面打了省略号,注意,这两个函数不是“可变参数函数哦~”),功能是这样:如果对象的值和expected一样,那么,就赋值成desired。而如果对象的值与expected不一样,则把expected的值赋值为现在对象的值。

    (我:其实用std::atomic<bool>作为例子来讲这两个参数稍微有点点晦涩,用int的话好理解多了)

    这两个函数的返回值都是bool类型,true代表store的操作进行了,false则没有进行。他们的区别在于:compare_exchange_weak可能对象值与expected一致,函数也可能会返回false,因为把desired赋值给对象会失败(特别是对于没有compare/exchange指令的CPU),失败的情况下不会更新std::atomic对象的值,compare_exchange_strong返回false则表示对象值与expected是不同的。

    (这两个参数还可以指定memory_older,说实话,现在我基本上没看明白,还是看完5.3再回来消化吧。)

    5.2.4 Operations on std::atomic<T*>: pointer arithmetic

    指向一个T对象的指针的原子类型。基本上和std::atomic<bool>,有着上面介绍的所有操作。但多出了一些“指针运算操作”。fetch_add()和fetch_sub(),就“前进”和“后退”相应的距离。与有+=,-=和前、后缀的++和--。注意的是fetch_xx返回的是原来的值(而不是运算后的值)。

    而+=,-=,++,--等的语义则与我们平常使用的指针是完全一致的。

    class Foo{};
    Foo some_array[5];
    std::atomic<Foo*> p(some_array);
    Foo* x=p.fetch_add(2); 
    assert(x==some_array);
    assert(p.load()==&some_array[2]);
    x=(p-=1); 
    assert(x==&some_array[1]);
    assert(p.load()==&some_array[1]);

    5.2.5 Operation on standard atomic integral types

    其它的整形的原子类型的操作基本上就比较相同了,放在这一节进行一个概述:(load(),  store(),  exchange(),  compare_exchange_weak(), and compare_exchange_strong())之类上面介绍的操作当然都是有的。也有像:fetch_add(), fetch_sub(), fetch_and(), fetch_or(),
    fetch_xor()这样的操作,分别代表了:(+=, -=, &=, |=, and ^=),还有前后缀的--,++。但没有乘,除和位运算。但由于原子类型一般主要用来计数,所以我们不会感觉到太多不便,实在需要的时候也可以使用compare_exchange_weak()加循环来得到。

    5.2.6 The std::atomic<> primary class template

    可以用atomic<>来做自定义的原子类型,但对于放入的模板参数有比较多的限制,我们可以这么认为:可以接受用浅拷贝和按位对比(bitwise compare)的类型才能作为atomic<T>中的T。(具体的说明请看原文)

    5.2.7 Free functions for atomic operations

    上面介绍的都是std::atomic的成员函数,其实它们都有相应对的free函数版本,支持情况如下表:

    image

    free函数设计得更为C一些,因此引用被换成了指针。

    另外,C++标准库为std::shared_ptr提供了一些重要的辅助函数,让这些智能指针可以以“原子操作”的方式获取值,设置值。

    std::shared_ptr<my_data> p;
    void process_global_data()
    {
        std::shared_ptr<my_data> local=std::atomic_load(&p);
        process_data(local);
    }
    void update_global_data()
    {
        std::shared_ptr<my_data> local(new my_data);
        std::atomic_store(&p,local);
    }

    它们都是以std::shared_ptr<>*作为第一个参数的,主要有:load,store,exchange和compare/exchange。

     

    ---- 总会有一个人需要你的分享~! 唐风: www.cnblogs.com/muxue ------
  • 相关阅读:
    Linux yum命令重装mysql
    Java多线程编程<一>
    Java内存区域与内存溢出异常
    实现一个线程安全的Queue队列
    Java 原始数据类型转换
    对象-关系映射ORM(Object Relational Mapping)(转)
    2进制,16进制,BCD,ascii,序列化对象相互转换
    Apache MINA 框架之默认session管理类实现
    Struts.properties(转)
    vue常用插件-数字滚动效果vue-count-to
  • 原文地址:https://www.cnblogs.com/muxue/p/3051491.html
Copyright © 2020-2023  润新知