C++并发编程(CH05)[内存模型初步&atomic-01&02]

Memory model basics

C++内存模型,包含有两个方面

  1. 对象的内存布局
  2. 多线程方面

Objects and memory locations

C++对象的定义是(内存中的一个区域)

struct my_data
{
    int i;
    double d;
    unsigned bf1:10;
    int bf2:25;
    int bf3:0;
    int bf4:9;
    int i2;
    char c1,c2;
    std::string s; 
};

这个冒号的意思是位域制定的意思.

  1. 定义的位域占比要小于类型.
  2. 不能对位域取地址.
#include <iostream>
struct S {
    // will usually occupy 2 bytes:
    // 3 bits: value of b1
    // 2 bits: unused
    // 6 bits: value of b2
    // 2 bits: value of b3
    // 3 bits: unused
    unsigned char b1 : 3, : 2, b2 : 6, b3 : 2;
};
int main()
{
    std::cout << sizeof(S) << '\n'; // usually prints 2
}

注意事项:

  1. Every variable is an object, including those that are members of
    other objects.
  2. Every object occupies at least one memory location.
  3. Variables of fundamental types such as int or char occupy exactly
    one memory location, whatever their size, even if they’re adjacent
    or part of an array.
  4. Adjacent bit fields are part of the same memory location.

Objects, memory locations, and concurrency

下面是C++的data race逻辑.

If there’s no enforced ordering between two accesses to a single
memory location from separate threads, one or both of those accesses is
not atomic, and if one or both is a write, then this is a data race and
causes undefined behavior.

Modification orders

Atomic operations and types in C++

An atomic operation is an indivisible operation.

The standard atomic types

  1. atomic可以在 <atomic>
    头文件里面找到.但是可能具体实现是用mutex来模拟的.所以你需要调用
    is_lock_free
    来判断是否是语言本身支持的原子类型.因为如果不是,那么你用它来写lock
    free的代码的话,效率不会很高.

  2. C++17含有一个static constexpr成员变量. X::is_always_lock_free
    可以用来判断这个类型是不是在任何情况下都是lock free的.

  3. ATOMIC_BOOL_LOCK_FREE, ATOMIC_CHAR_LOCK_FREE,
    ATOMIC_CHAR16_T_LOCK_FREE, ATOMIC_CHAR32_T_LOCK_FREE,
    ATOMIC_WCHAR_T_LOCK_FREE,ATOMIC_SHORT_LOCK_FREE,
    ATOMIC_INT_LOCK_FREE, ATOMIC_LONG_LOCK_FREE,
    ATOMIC_LLONG_LOCK_FREE, and ATOMIC_POINTER_LOCK_FREE.
    这些宏也是用来判断基本类型的lock free属性的.

    • 0 if the atomic type is never lock-free,
    • 2 if the atomic type is always lock-free,
    • 1 if the lock-free status of the corre-sponding atomic type is a
      runtime property as described previously.
  4. std::atomic_flag. 没有 is_lock_free 因为它必须要是lock free的.

  5. std::atomic<int> and std::atomic<void*>也应该是lock free的.

  6. 这些是基本原子类型的别名.不建议混合使用.最好编程风格要统一.

    Atomic type Corresponding specialization


    atomic_bool std::atomic<bool>
    atomic_char std::atomic<char>
    atomic_schar std::atomic<signed char>
    atomic_uchar std::atomic<unsigned char>
    atomic_int std::atomic<int>
    atomic_uint std::atomic<unsigned>
    atomic_short std::atomic<short>
    atomic_ushort std::atomic<unsigned short>
    atomic_long std::atomic<long>
    atomic_ulong std::atomic<unsigned long>
    atomic_llong std::atomic<long long>
    atomic_ullong std::atomic<unsigned long long>
    atomic_char16_t std::atomic<char16_t>
    atomic_char32_t std::atomic<char32_t>
    atomic_wchar_t std::atomic<wchar_t>

  7. 一些非atomic标准的其他类型的atomic类型.

    Atomic typedef Corresponding Standard Library typedef


    atomic_int_least8_t int_least8_t
    atomic_uint_least8_t uint_least8_t
    atomic_int_least16_t int_least16_t
    atomic_uint_least16_t uint_least16_t
    atomic_int_least32_t int_least32_t
    atomic_uint_least32_t uint_least32_t
    atomic_int_least64_t int_least64_t
    atomic_uint_least64_t uint_least64_t
    atomic_int_fast8_t int_fast8_t
    atomic_uint_fast8_t uint_fast8_t
    atomic_int_fast16_t int_fast16_t
    atomic_uint_fast16_t uint_fast16_t
    atomic_int_fast32_t int_fast32_t
    atomic_uint_fast32_t uint_fast32_t
    atomic_int_fast64_t int_fast64_t
    atomic_uint_fast64_t uint_fast64_t
    atomic_intptr_t intptr_t
    atomic_uintptr_t uintptr_t
    atomic_size_t size_t
    atomic_ptrdiff_t ptrdiff_t
    atomic_intmax_t intmax_t
    atomic_uintmax_t uintmax_t

  8. 标准的atomic类型,不允许拷贝和赋值.因为他们没有copy constructor和copy
    assignment.

  9. 但是他们都含有下面这些接口

    • load()

    • store()

    • exchange()

    • compare_exchange_weak()

    • compare_exchange_strong()

    • 修改赋值

      +=, -=,*=,|=, and so on
      

      他们都有函数名与之配对.(eg:fetch_add, fetch_or())

  10. 你也可以用 std:atomic<>
    来定义用户类型的原子类型.但是用户类型的原子类型,就没有那么多功能了.the
    operations are limited to load(), store() (and assignment from and
    conversion to the user-defined type), exchange(),
    compare_exchange_weak(), and compare_exchange_strong().

  11. 每一个原子类型操作,都有一个参数是指定内存顺序的.他们是std::memory_order
    enumeration.它含有6个枚举类型

    • std::memory_order_relaxed,
      没有顺序限制,只是保证此次操作是原子操作
    • std::memory_order_acquire,
      本线程中,所有后续的读操作不能reorder到这个原子类型之前
    • std::memory_order_consume,
      本线程中,所有后续的有关本原子类型的操作,不能reorder这条语句之前.
    • std::memory_order_acq_rel, 同时包含 memory_order_acquire
      和 memory_order_release
    • std::memory_order_release,
      本线程中,所有之前的写操作不能reorder这条语句之后.
    • std::memory_order_seq_cst. 全部存取都按顺序执行

PS: 为什么要制定内存顺序?

处理器乱序执行和编译器指令重排可能造成数据读写顺序错乱,CPU缓存可能造成数据更新不及时,memory_order的作用是明确数据的读写顺序以及数据的写入对其它线程的可见性。

Operations on std::atomic_flag

std::atomic_flag must be initialized with ATOMIC_FLAG_INIT
这是一个clear state

std::atomic_flag f=ATOMIC_FLAG_INIT;

当你设置好之后,你只能采取三项措施

  1. destroy it,
  2. clear it,
  3. set it and query the previous value.
f.clear(std::memory_order_release);
bool x=f.test_and_set();                               

注意:atomic类型不能进行从一个atomic拷贝到另一个或者进行赋值.这会牵涉两个对象参与此次操作.而一个操作牵涉两个对象,就不可能是atomic的.

// 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);
    }
};

注意 atomix_flag
的功能非常有限.你不能进行无修改的query.但是下面的可以.

Operations on std::atomic<bool>

std::atomic<bool> b(true);
b=false; // 注意这个赋值语句返回的是false本身.不是一个atomic<bool>的引用.这是为了防止多线程竞争的问题.

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

STORING A NEW VALUE (OR NOT) DEPENDING ON THE CURRENT VALUE

bool expected=false;
extern atomic<bool> b; // set somewhere else
while(!b.compare_exchange_weak(expected,true) && !expected);
  1. weak
    • b和exprected比较.如果相等那么就讲b替换为后面true.然后返回true
    • 如果b和expected不等,则将expected赋值为b所含有的值.饭后返回false
    • 即便b和expected相等,也可能没有完成替换为true.这样就会返回false.
  2. strong
    满足上面的过程,但是false只能是b不等于expected.其他都会返回true.也就是替换肯定可以发生.效率相对低一点儿.
std::atomic<bool> b;
bool expected;
b.compare_exchange_weak(expected,true,
                        memory_order_acq_rel,memory_order_acquire);
b.compare_exchange_weak(expected,true,memory_order_acq_rel);
  1. 另外,你可以给失败和成功各自制定memory
    order.如果不指定,fail.那么fail就是和success一样的.
  2. atomic<bool>atomic_flag 不同之处在于,前者可能不是lock
    free的.因为前者可能在不同的实现里面采用了mutex来进行内部处理.

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

x.fetch_add(3) 会讲x按照指针移动三个位置,并返回原先的指针.

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]);

你也可以指定内存顺序

p.fetch_add(3,std::memory_order_release);

Operations on standard atomic integral types

对于atomic integral types such as std::atomic<int> or
std::atomic<unsigned long long> 还可以有下面的操作: fetch_add(),
fetch_sub(), fetch_and(), fetch_or(),fetch_xor()

其中,除法,乘法,移位没有提供.因为atomic应该用作计数和bitmask.所以这些操作不应该有.但是如果你需要可以使用
compare_exchange_weak() in a loop 来也行.

The std::atomic<> primary class template

  1. user-defined type UDT, std::atomic<UDT> provides the same
    interface as std::atomic<bool>

  2. You can’t use just any user-defined type with std::atomic<>,
    though; the type has to fulfill certain criteria.

  3. 限制

    • the type must not have any virtual functions or virtual base
      classes and must use the compiler-generated copy-assignment
      operator.

    • every base class and non-static data member of a user-defined
      type must also have a trivial copy-assignment operator. This
      permits the compiler to use memcpy() or an equivalent operation
      for assignment operations, because there’s no user-written code
      to run.

    • it is worth noting that the compare-exchange operations do
      bitwise com- parison as if using memcmp, rather than using any
      comparison operator that maybe defined for UDT. If the type
      provides comparison operations that have different semantics, or
      the type has padding bits that do not participate in normal
      comparisons, then this can lead to a compare-exchange operation
      failing, even though the values compare equally.

  4. 之所以有这些限制,主要是因为atomic实际上使用memcmp操作等.是按位操作.

If your UDT is the same size as (or smaller than) an int or a void*,
most common platforms will be able to use atomic instructions for
std::atomic<UDT>. Some platforms will also be able to use atomic
instructions for user-defined types that are twice the size of an int or
void*. These platforms are typically those that support a so-called
double-word-compare-and-swap (DWCAS) instruction corresponding to the
compare_exchange_xxx functions.

you can’t, for example, create std::atomic<std::vector<int>>
(because it has a non-trivial copy constructor and copy assignment
operator)

  1. 支持的变量类型

                                                  atomic              atomic
                  `atomic_`   atomic     atomic                       

Operation <integral-type> <other-type>
flag <bool> <T*>
test_and_set Y
clear Y
is_lock_free Y Y Y Y
load Y Y Y Y
store Y Y Y Y
exchange Y Y Y Y
compare_exchange Y Y Y Y
_weak, compare_
exchange_strong
fetch_add, Y Y
fetch_sub, Y Y
fetch_or, Y
fetch_and, Y
fetch_xor, Y
++, – Y Y


Free functions for atomic operations

std::atomic_store(&atomic_var,new_value) 
std::atomic_store_explicit(&atomic_var,new_value,std::memory_order_release).
std::atomic_is_lock_free()

这些自由函数是用来兼容C的.另外可以调用有explicit后缀的函数来制定内存顺序.还有可以用在share指针上.

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::exprimental::atomic_shared_ptr<T>

提供了lock free的额外功能.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值