精华内容
下载资源
问答
  • 所有原子结构示意 (1)

    千次阅读 2021-01-13 01:32:45
    更多好内容为您奉上所有原子结构示意+11氢H+22氦He+321锂Li+422铍Be+523硼B+624碳C+725氮N+826氧O+927氟F+1028氖Ne+11281钠Na+12282镁Mg+13283铝Al+14284硅Si+15285磷P+16286硫S+17287氯Cl+18288氩Ar+192881钾K+...

    更多好内容为您奉上

    所有原子结构示意图

    +1

    1

    H

    +2

    2

    He

    +3

    2 1

    Li

    +4

    2 2

    Be

    +5

    2 3

    B

    +6

    2 4

    C

    +7

    2 5

    N

    +8

    2 6

    O

    +9

    2 7

    F

    +10

    2 8

    Ne

    +11

    2 8 1

    Na

    +12

    2 8 2

    Mg

    +13

    2 8 3

    Al

    +14

    2 8 4

    Si

    +15

    2 8 5

    P

    +16

    2 8 6

    S

    +17

    2 8 7

    Cl

    +18

    2 8 8

    Ar

    +19

    2 8 8 1

    K

    +20

    2 8 8 2

    Ca

    +21

    2 8 9 2

    Sc

    +22

    2 8 10 2

    Ti

    +23

    2 8 11 2

    V

    +24

    2 8 13 1

    Cr

    +25

    2 8 13 2

    Mn

    +26

    2 8 14 2

    Fe

    +27

    2 8 15 2

    Co

    +28

    2 8 16 2

    Ni

    +29

    2 8 18 1

    Cu

    +30

    2 8 18 2

    Zn

    +31

    2 8 18 3

    Ga

    +32

    2 8 18 4

    Ge

    +33

    2 8 18 5

    As

    +34

    2 8 18 6

    Se

    +35

    2 8 18 7

    Br

    +36

    2 8 18 8

    Kr

    +37

    2 8 18 8 1

    Rb

    +38

    2 8 18 8 2

    Sr

    +39

    2 8 18 9 2

    Y

    +40

    2 8 18 10 2

    Zr

    +41

    2 8 18 12 1

    Nb

    +42

    2 8 18 13 1

    Mo

    +43

    2 8 18 13 2

    Tc

    +44

    2 8 18 15 1

    Ru

    +45

    2 8 18 16 1

    Rh

    +46

    2 8 18 18

    Pd

    +47

    2 8 18 18 1

    Ag

    +48

    2 8 18 18 2

    Cd

    +49

    2 8 18 18 3

    In

    +50

    2 8 18 18 4

    Sn

    +51

    2 8 18 18 5

    Sb

    +52

    2 8 18 18 6

    Te

    +53

    2 8 18 18 7

    I

    +54

    2 8 18 18 8

    Xe

    +55

    2 8 18 18 8 1

    Cs

    +56

    2 8 18 18 8 2

    Ba

    +57

    2 8 18 18 9 2

    La

    +58

    2 8 18 19 9 2

    Ce

    +59

    2 8 18 21 8 2

    Pr

    +60

    2 8 18 22 8 2

    Nd

    +61

    2 8 18 23 8 2

    Pm

    +62

    2 8 18 24 8 2

    Sm

    +63

    2 8 18 25 8 2

    Eu

    +64

    2 8 18 25 9 2

    Gd

    +65

    2 8 18 27 8 2

    Td

    +66

    2 8 18 28 8 2

    Dy

    +67

    2 8 18 29 8 2

    Ho

    +68

    2 8 18 30 8 2

    Er

    +69

    2 8 18 31 8 2

    Tm

    +70

    2 8 18 32 8 2

    Yb

    +71

    2 8 18 32 9 2

    Lu

    +72

    2 8 18 32 10 2

    Hf

    +73

    2 8 18 32 11 2

    Ta

    +74

    2 8 18 32 12 2

    W

    +75

    2 8 18 32 13 2

    Re

    +76

    2 8 18 32 14 2

    Os

    +77

    2 8 18 32 15 2

    Ir

    +78

    2 8 18 32 17 1

    Pt

    +79

    2 8 18 32 18 1

    Au

    +80

    2 8 18 32 18 2

    Hg

    +81

    2 8 18 32 18 3

    Tl

    +82

    2 8 18 32 18 4

    Pb

    +83

    2 8 18 32 18 5

    Bi

    +84

    2 8 18 32 18 6

    展开全文
  • 原子结构示意的分类和详细知识点2013-04-14 22:17:22...那么高中网校的化学老师称,关于原子结构示意我们应首先掌握原子结构示的分类,那么本文中酷课网老师就详细为同学们介绍一下原子结构示意的分类和详细知...

    原子结构示意图的分类和详细知识点

    2013-04-14 22:17:22 来源:http://www.51cok.com/          点击:141次

    分享到:

    我们在学习化学知识点的时候应首先学好原子结构示意图的内容,这将有助于同学们学习分子的知识点。那么高中网校的化学老师称,关于原子结构示意图我们应首先掌握原子结构示的分类,那么本文中酷课网老师就详细为同学们介绍一下原子结构示意图的分类和详细知识点。

    太阳系模型

    英国物理学家欧内斯特·卢瑟福(Ernest Rutherford,1871~1937)1895年来到英国卡文迪许实验室,跟随汤姆逊学习,成为汤姆逊第一位来自海外的研究生。卢瑟福好学勤奋,在汤姆逊的指导下,卢瑟福在做他的第一个实验——放射性吸收实验时发现了α射线。

    卢瑟福设计的巧妙的实验,他把铀、镭等放射性元素放在一个铅制的容器里,在铅容器上只留一个小孔。由于铅能挡住放射线,所以只有一小部分射线从小孔中射出来,成一束很窄的放射线。卢瑟福在放射线束附近放了一块很强的磁铁,结果发现有一种射线不受磁铁的影响,保持直线行进。第二种射线受磁铁的影响,偏向一边,但偏转得不厉害。第三种射线偏转得很厉害。

    卢瑟福在放射线的前进方向放不同厚度的材料,观察射线被吸收的情况。第一种射线不受磁场的影响,说明它是不带电的,而且有很强的穿透力,一般的材料如纸、木片之类的东西都挡不住射线的前进,只有比较厚的铅板才可以把它完全挡住,称为γ射线。第二种射线会受到磁场的影响而偏向一边,从磁场的方向可判断出这种射线是带正电的,这种射线的穿透力很弱,只要用一张纸就可以完全挡住它。这就是卢瑟福发现的α射线。第三种射线由偏转方向断定是带负电的,性质同快速运动的电子一样,称为β射线。卢瑟福对他自己发现的α射线特别感兴趣。他经过深入细致的研究后指出,α射线是带正电的粒子流,这些粒子是氦原子的离子,即少掉两个电子的氦原子。

    “计数管”是来自德国的学生汉斯·盖革(Hans Geiger,1882-1945))发明的,可用来测量肉眼看不见的带电微粒。当带电微粒穿过计数管时,计数管就发出一个电讯号,将这个电讯号连到报警器上,仪器就会发出“咔嚓”一响,指示灯也会亮一下。看不见摸不着的射线就可以用非常简单的仪器记录测量了。人们把这个仪器称为盖革计数管。藉助于盖革计数管,卢瑟福所领导的曼彻斯特实验室对α粒子性质的研究得到了迅速的发展。

    1910年马斯登(E.Marsden,1889-1970)来到曼彻斯特大学,卢瑟福让他用α粒子去轰击金箔,做练习实验,利用荧光屏记录那些穿过金箔的α粒子。按照汤姆逊的葡萄干蛋糕模型,质量微小的电子分布在均匀的带正电的物质中,而α粒子是失去两个电子的氦原子,它的质量要比电子大几千倍。当这样一颗重型炮弹轰击原子时,小小的电子是抵挡不住的。而金原子中的正物质均匀分布在整个原子体积中,也不可能抵挡住α粒子的轰击。也就是说,α粒子将很容易地穿过金箔,即使受到一点阻挡的话,也仅仅是α粒子穿过金箔后稍微改变一下前进的方向而已。这类实验,卢瑟福和盖革已经做过多次,他们的观测结果和汤姆逊的葡萄干蛋糕模型符合得很好。α粒子受金原子的影响稍微改变了方向,它的散射角度极小。

    展开全文
  • 文章目录第五章——内存模型原子操作5.1 内存模型5.1.1 对象和内存位置5.1.2 对象,内存位置和并发5.1.3 修改顺序5.2 原子操作和原子类型5.2.1 标准原子类型5.2.2 std::atomic_flag5.2.3 std::atomic\5.2.4 std::...

    第五章——内存模型和原子操作

    5.1 内存模型

    C++所有的对象都和内存位置有关

    5.1.1 对象和内存位置

    C++程序中数据都是由对象构成

    无论是怎么样的类型,都会存储在一个或多个内存位置上。每个内存位置不是标量类型的对象,就是标量类型的子对象

    这里有四个需要牢记的原则

    1. 每个变量都是对象,包括其成员变量的对象。
    2. 每个对象至少占有一个内存位置。
    3. 基本类型都有确定的内存位置(无论类型大小如何,即使他们是相邻的,或是数组的一部分)。
    4. 相邻位域是相同内存中的一部分。

    5.1.2 对象,内存位置和并发

    当两个线程访问不同的内存位置时,不会存在任何问题,当两个线程访问同一个内存位置就要小心了。如果线程不更新数据,只读数据不需要保护或同步。当线程对内存位置上的数据进行修改,就可能会产生条件竞争

    为了避免条件竞争,线程就要以一定的顺序执行。第一种方式,使用互斥量来确定访问的顺序。当同一互斥量在两个线程同时访问前锁住,那么在同一时间内就只有一个线程能够访问对应的内存位置另一种是使用原子操作决定两个线程的访问顺序,当多个线程访问同一个内存地址时,对每个访问者都需要设定顺序

    当程序对同一内存地址中的数据访问存在竞争,可以使用原子操作来避免未定义行为。当然,这不会影响竞争的产生——原子操作并没有指定访问顺序——而原子操作会把程序拉回到定义行为的区域内。

    5.1.3 修改顺序

    每一个在C++程序中的对象,都有确定好的修改顺序,在初始化开始阶段确定。大多数情况下,这个顺序不同于执行中的顺序,但是在给定的执行程序中,所有线程都需要遵守这个顺序

    如果对象不是一个原子类型,你必须要确保有足够的同步操作,来确定每个线程都遵守了变量的修改顺序。当不同线程在不同序列中访问同一个值时,你可能就会遇到数据竞争或未定义行为。如果你使用原子操作,编译器就有责任去替你做必要的同步。

    这一要求意味着,投机执行是不允许的,因为当线程按修改顺序访问一个特殊的输入,之后的读操作,必须由线程返回较新的值,并且之后的写操作必须发生在修改顺序之后。同样的,在同一线程上允许读取对象的操作,要不返回一个已写入的值,要不在对象的修改顺序后再写入另一个值。

    5.2 原子操作和原子类型

    原子操作是个不可分割的操作。系统的所有线程中,不可能观察到原子操作完成了一半。如果读取对象的加载操作是原子的,那么这个对象的所有修改操作也是原子的,所以加载操作得到的值要么是对象的初始值,要么是某次修改操作存入的值。

    另一方面,非原子操作可能会被另一个线程观察到只完成一半。如果这个操作是一个存储操作,那么其他线程看到的值,可能既不是存储前的值,也不是存储的值。如果非原子操作是一个读取操作,可能先取到对象的一部分,然后值被另一个线程修改,然后它再取到剩余的部分,所以它取到的既不是第一个值,也不是第二个值。这就构成了数据竞争,出现未定义行为。

    5.2.1 标准原子类型

    标准原子类型定义在头文件<atomic>中。这些类型的操作都是原子的,语言定义中只有这些类型的操作是原子的,也可以用互斥锁来模拟原子操作。标准原子类型的实现可能是这样的:它们(几乎)都有一个is_lock_free()成员函数,这个函数可以让用户查询某原子类型的操作是直接用的原子指令(x.is_lock_free()返回true),还是内部用了一个锁结构(x.is_lock_free()返回false)。

    原子操作可以替代互斥量,来完成同步操作。如果操作内部使用互斥量实现,那么不可能有性能的提升。所以要对原子操作进行实现,最好使用不基于互斥量的实现

    标准库提供了一组宏,在编译时对各种整型原子操作是否无锁进行判别。C++17中,所有原子类型有一个static constexpr成员变量,如果相应硬件上的原子类型X是无锁类型,那么X::is_always_lock_free将返回true。例如:给定目标硬件平台std::atomic<int>无锁,那么std::atomic<int>::is_always_lock_free将会返回true。不过std::atomic<uintmax_t>因为这是一个运行时属性,所以std::atomic<uintmax_t>::is_always_lock_free在该平台编译时可能为 false

    宏都有ATOMIC_BOOL_LOCK_FREE , ATOMIC_CHAR_LOCK_FREE , ATOMIC_CHAR16_T_LOCK_FREE , ATOMIC_CHAR32_T_LOCK_FREEATOMIC_WCHAR_T_LOCK_FREEATOMIC_SHORT_LOCK_FREE , ATOMIC_INT_LOCK_FREE , ATOMIC_LONG_LOCK_FREE , ATOMIC_LLONG_LOCK_FREEATOMIC_POINTER_LOCK_FREE它们指定了内置原子类型的无锁状态和无符号对应类型(LLONG对应long longPOINTER对应所有指针类型)。如果原子类型不是无锁结构,那么值为0。如果原子类型是无锁结构,那么值为2。如果原子类型的无锁状态在运行时才能确定,那么值为1

    只有std::atomic_flag类型不提供 is_lock_free()。该类型是一个简单的布尔标志,并且在这种类型上的操作都是无锁的。当有一个简单无锁的布尔标志时,可以使用该类型实现一个简单的锁,并且可以实现其他基础原子类型。对std::atomic_flag明确初始化后,做查询和设置(使用test_and_set()成员函数),或清除(使用clear()成员函数)都很容易:无赋值,无拷贝,没有测试和清除,没有任何多余操作。

    剩下的原子类型都可以通过特化std::atomic<>得到,并且拥有更多的功能,但不可能都是无锁的

    通常,标准原子类型不能进行拷贝和赋值,它们没有拷贝构造函数和拷贝赋值操作符。但是,可以隐式转化成对应的内置类型,所以这些类型依旧支持赋值,可以使用load()store()exchange()compare_exchange_weak()compare_exchange_strong()。它们都支持复合赋值符:+=, -=, *=, |=等等。并且使用整型和指针的特化类型还支持++--操作。当然,这些操作也有功能相同的成员函数所对应:fetch_add(), fetch_or()等等。赋值操作和成员函数的返回值,要么是存储值(赋值操作),要么是操作值(命名函数),这就能避免赋值操作符返回引用。

    std::atomic<>类模板不仅仅是一套可特化的类型,作为原发模板也可以使用自定义类型创建对应的原子变量。因为是通用类模板,操作限制为load()store()(赋值和转换为用户类型),exchange()compare_exchange_weak()compare_exchange_strong()

    每种函数类型的操作都有一个内存序参数,这个参数可以用来指定存储的顺序

    • Store操作,可选如下内存序:memory_order_relaxed, memory_order_release, memory_order_seq_cst
    • Load操作,可选如下内存序:memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_seq_cst
    • Read-modify-write(读-改-写)操作:可选如下内存序:memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst

    5.2.2 std::atomic_flag

    std::atomic_flag是最简单的原子类型,这个类型的对象可以在两个状态间切换:设置和清除。就是这么简单,只作为构建块存在

    std::atomic_flag类型的对象必须被ATOMIC_FLAG_INIT初始化。初始化标志位是“清除”状态。这里没得选择,这个标志总是初始化为“清除”:

    std::atomic_flag f = ATOMIC_FLAG_INIT;
    

    这适用于任何对象的声明,是唯一需要以如此特殊的方式初始化的原子类型,但也是唯一保证无锁的类型。首次使用时,需要初始化。如果std::atomic_flag是静态存储的,那么就得保证其是静态初始化的,也就意味着没有初始化顺序问题。

    当标志对象已初始化,只能做三件事情:销毁,清除或设置(查询之前的值)。这些操作对应的函数分别是:clear()成员函数和test_and_set()成员函数

    clear()test_and_set()成员函数可以指定好内存顺序。clear()是一个存储操作,所以不能有memory_order_acquirememory_order_acq_rel语义,但test_and_set()是一个“读-改-写”操作,可以应用于任何内存顺序。每一个原子操作,默认的内存序都是memory_order_seq_cst

    f.clear(std::memory_order_release);  // 1
    bool x=f.test_and_set();  // 2
    

    调用clear()①明确要求,使用释放语义清除标志,当调用test_and_set()②使用默认内存序设置表示,并且检索旧值。

    不能拷贝构造std::atomic_flag对象,不能将一个对象赋予另一个std::atomic_flag对象。这不是std::atomic_flag特有的属性,而是所有原子类型共有的属性。原子类型的所有操作都是原子的,而赋值和拷贝调用了两个对象,这就就破坏了操作的原子性。这样的话,拷贝构造和拷贝赋值都会将第一个对象的值进行读取,然后再写入另外一个。对于两个独立的对象,这里就有两个独立的操作了,合并这两个操作必定是不原子的。因此,操作就不被允许。

    有限的特性使得std::atomic_flag非常适合于作自旋锁。初始化标志是“清除”,并且互斥量处于解锁状态。为了锁上互斥量,循环运行test_and_set()直到旧值为false,就意味着这个线程已经被设置为true了。解锁互斥量是一件很简单的事情,将标志清除即可。

    //代码5.1 使用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);
      }
    };
    

    5.2.3 std::atomic<bool>

    最基本的原子整型类型就是std::atomic<bool>,它有着比std::atomic_flag更加齐全的布尔标志特性。虽然不能拷贝构造和拷贝赋值,但可以使用非原子的bool类型进行构造,所以可以初始化为truefalse,并且可以从非原子bool变量赋值给std::atomic<bool>

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

    非原子bool类型的赋值操作不同于通常的操作(转换成对应类型的引用,再赋给对应的对象):它返回一个bool值来代替指定对象。原子类型中的另一种模式:通过返回值(返回相关的非原子类型)完成赋值。如果原子变量的引用返回了,任何依赖与这个赋值结果的代码都需要显式加载。问题是,结果可能会被其他线程修改。通过返回非原子值进行赋值的方式,可以避免多余的加载过程,并得到实际存储的值。

    虽然有内存序的指定,但使用store()写入(truefalse)还是好于std::atomic_flag中的clear()。同样,test_and_set()也可以替换为更加通用的exchange()exchange()允许使用新选的值替换已存储的值,并且会自动检索原始值std::atomic<bool>也支持对值的(不可修改)查找,其会将对象隐式的转换为普通的bool值,或显示的调用load()来完成。store()是一个存储操作,而load()是一个加载操作,exchange()是一个“读-改-写”操作

    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<bool>提供多个“读-改-写”的操作,exchange()只是其中之一。它还介绍了一种新的存储方式:当前值与预期值一致时,存储新值的操作

    存储一个新值(或旧值)取决于当前值

    这种新型操作叫做“比较/交换”,它的形式表现为compare_exchange_weak()compare_exchange_strong()

    “比较/交换”操作是原子类型编程的基石,它比较原子变量的当前值和期望值,当两值相等时,存储所提供值。当两值不等,期望值就会被更新为原子变量中的值。“比较/交换”函数值是一个bool变量,当返回true时执行存储操作,false则更新期望值。当存储完成(因为只相等),则操作是成功的,否则即为失败。操作成功时返回true,失败时返回false

    对于compare_exchange_weak(),当原始值与预期值一致时,存储也可能会不成功。在这种情况中变量的值不会发生改变,并且compare_exchange_weak()的返回值是false。这最可能发生在缺少单条CAS操作(“比较-交换”指令)的机器上,当处理器不能保证这个操作能够原子的完成——可能因为线程的操作执行到必要操作的中间时被切换,并且另一个线程将会被操作系统调度(这里线程数多于处理器数量),称为“伪失败”(spurious failure),因为造成这种情况的是时间,而不是变量值。

    因为compare_exchange_weak()可以伪失败,所以通常会配合一个循环使用:

    bool expected=false;
    extern atomic<bool> b; // 设置些什么
    while(!b.compare_exchange_weak(expected,true) && !expected);
    

    这个例子中,循环中expected的值始终是false,表示compare_exchange_weak()会莫名的失败。

    另一方面,当实际值与expected不符,compare_exchange_strong()就能保证值返回false。这就能消除对循环的需要,就可以知道是否成功的改变了一个变量,或已让另一个线程完成

    如果只想要不管atomic变量的初始值并改变它的变量值,对expected 的更新将会变更有用;经历每次循环的时候,expected都会重新加载,所以当没有其他线程同时修改expected时,循环中对compare_exchange_weak()compare_exchange_strong()的调用都会在下一次(第二次)成功。如果值很容易存储,使用compare_exchange_weak()能更好的避免一个双重循环的执行,即使compare_exchange_weak()可能会“伪失败”(因此compare_exchange_strong()包含一个循环)。另一方面,如果值的存储本身非常耗时,当期望值不变时,使用compare_exchange_strong()可以避免对值的重复计算。对于std::atomic<bool>这些都不重要——毕竟只有两种值——但是对于其他的原子类型影响就比较大了。

    compare/exchange”另一点不同的是,它拥有对两个内存序的参数进行操作的能力,这就允许内存序语义在成功和失败的例子中有所不同。可能成功时使用memory_order_acq_rel,而失败时使用memory_order_relaxed。失败的“compare/exchange”将不会进行存储,所以“compare/exchange”操作不能拥有meory_order_releasememory_order_acq_rel。因此,不保证这些值能作为失败的顺序,也不能提供比成功内存序更加严格的失败内存序,当memory_order_acquirememory_order_seq_cst作为失败时的内存序时,也要为成功时指定内存序。

    5.2.4 std::atmoic<T*>

    原子指针类型,可以使用内置类型或自定义类型T,通过特化std::atomic<T*>进行定义,操作是针对于相关类型的指针。虽然既不能拷贝构造,也不能拷贝赋值,但是可以通过合适的类型指针进行构造和赋值。std::atomic<T*>也有load(), store(), exchange(), compare_exchange_weak()compare_exchage_strong()成员函数,获取与返回的类型都是T*

    std::atomic<T*>为指针运算提供新的操作。基本操作有fetch_add()fetch_sub(),它们在存储地址上做原子加法和减法,为+=, -=, ++--提供简易的封装。对于内置类型的操作,例如:如果xstd::atomic<Foo*>类型的数组的首地址,然后x+=3让其偏移到第四个元素的地址,并返回一个普通的Foo*类型值,这个指针值是指向数组中第四个元素。fetch_add()fetch_sub()的返回值略有不同(所以x.fetch_add(3)x指向第四个元素,并且函数返回指向第一个元素的地址)。这种操作也被称为“交换-相加”,并且这是一个原子的“读-改-写”操作,如同exchange()compare_exchange_weak()/compare_exchange_strong()一样。正像其他操作那样,返回值是一个普通的T*值,而非是std::atomic<T*>对象的引用,所以调用代码可以基于之前的值进行操作:

    class Foo{};
    Foo some_array[5];
    std::atomic<Foo*> p(some_array);
    Foo* x=p.fetch_add(2);  // p加2,并返回原始值
    assert(x==some_array);
    assert(p.load()==&some_array[2]);
    x=(p-=1);  // p减1,并返回原始值
    assert(x==&some_array[1]);
    assert(p.load()==&some_array[1]);
    

    函数也允许内存序作为给定函数的参数:

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

    因为fetch_add()fetch_sub()都是“读-改-写”操作,可以使用任意的内存序,以及加入到一个释放序列中。因为没办法提供必要的信息(这些形式都具有memory_order_seq_cst语义),所以指定的语序不支持操作符形式。

    剩下的原子类型基本上都差不多:它们都是整型原子类型,并且拥有同样的接口(除了内置类型不一样)。

    5.2.5 标准原子整型的相关操作

    如同普通的操作集合一样(load(), store(), exchange(), compare_exchange_weak(), 和compare_exchange_strong()),std::atomic<int>std::atomic<unsigned long long>也是有一套完整的操作可以供使用:fetch_add(), fetch_sub(), fetch_and(), fetch_or(), fetch_xor(),还有复合赋值方式((+=, -=, &=, |=和^=),以及++--(++x, x++, --x和x--)。虽然对于普通的整型来说,这些复合赋值方式还不完全:除法、乘法和移位操作不在其中。因为,整型原子值通常用来作计数器,或者是掩码,所以以上操作的缺失显得不是那么重要。如果需要,可以使用compare_exchange_weak()完成。

    对于std::atomic<T*>类型,紧密相关的两个函数就是fetch_add()fetch_sub()。函数原子化操作,并且返回旧值,而符合赋值运算会返回新值。前缀加减和后缀加减与普通用法一样:++x对变量进行自加,并且返回新值;而x++对变量自加,返回旧值。这两个例子中,结果都是整型相关的一个值。

    5.3 同步操作和强制排序

    假设两个线程,一个向数据结构中填充数据,另一个读取数据结构中的数据。为了避免恶性条件竞争,第一个线程设置一个标志,用来表明数据已经准备就绪,从而第二个线程在这个标志设置前不能读取数据。

    //代码5.2 不同线程对数据的读写
    #include <vector>
    #include <atomic>
    #include <iostream>
    
    std::vector<int> data;
    std::atomic<bool> data_ready(false);
    
    void reader_thread()
    {
      while(!data_ready.load())  // 1
      {
        std::this_thread::sleep(std::milliseconds(1));
      }
      std::cout<<"The answer="<<data[0]<<"\m";  // 2
    }
    void writer_thread()
    {
      data.push_back(42);  // 3
      data_ready=true;  // 4
    }
    

    先把等待数据的循环①放在一边(因为每一个数据项都必须是原子的,所以这个循环不会在线程间产生数据共享)。当非原子读②和写③对同一数据结构进行无序访问时,破坏了循环遵守的访问顺序,所以会产生未定义行为。

    访问顺序通过对std::atomic<bool>类型的data_ready变量进行操作完成,这些操作通过先行同发确定顺序。写入数据③在写入data_ready④前发生,读取①发生在读取数据②之前。当data_ready①为true,写操作就会与读操作同步,建立一个“先行”的关系。因为“先行”关系是可传递的,所以写入③先行于写入④,这两个行为又先行于读取操作①,之前的操作都先行于读取数据②,这样就强制了顺序:写入数据先行于读取数据。

    5.3.1 同步发生

    “同步发生”只在原子类型之间进行。例如:操作一个数据结构(对互斥量上锁),如果数据结构包含有原子类型,并且操作内部执行了一定的原子操作,那这些操作就是“同步发生”关系。从根本上说,这种关系只能来源于对原子类型的操作。

    “同步发生”的基本想法是:原子写操作W对变量x进行标记,同步与对x进行原子读操作,读取的是W操作写入的内容,或是W之后,同一线程上的原子写操作对x写入的值,亦或是任意线程对x的一系列原子读-改-写操作(例如,fetch_add()compare_exchange_weak()

    因为对原子类型的操作默认都有“适当的标记”,如果线程A存储了一个值,并且线程B读取了这个值,线程A的存储操作与线程B的载入操作就是同步发生关系。

    5.3.2 先行发生

    “先行发生”关系是一个程序中基本构建块的操作顺序:指定了某个操作去影响另一个操作。对于单线程来说:一个操作排在另一个之后,那这个操作就先执行。如果源码中操作A发生在操作B之前,那A就先行于B。可以回看代码5.2:对data的写入③先于对data_ready④的写入。如果操作在同时发生,因为操作间无序执行,通常情况下就没有先行关系了。

    //代码5.3 对于参数中的函数调用顺序未指定顺序
    #include <iostream>
    void foo(int a,int b)
    {
      std::cout<<a<<”,”<<b<<std::endl;
    }
    int get_num()
    {
      static int i=0;
      return ++i;
    }
    int main()
    {
      foo(get_num(),get_num());  // 无序调用get_num()
    }
    

    线程间的先行比较简单,并且依赖于同步关系(详见5.3.1节):如果操作A在一个线程上,与另一个线程上的操作B同步,那么A就线程间先行于B。这也是一个传递关系:如果A线程间先行于B,并且B线程间先行于C,那么A就线程间先行于C。

    线程间先行可以与排序先行相结合:如果操作A排序先行于操作B,并且操作B线程间先行于操作C,那么A线程间先行于C。同样的,如果A同步于B,并且B排序先于C,那么A线程间先行于C。当对数据进行一系列修改(单线程)时,只需要对数据进行一次同步即可。

    强先行发生关系会有一些不同,不过在大多数情况下是一样的。如果操作A与操作B同步,或操作A的顺序在操作B之前,那么A就是强先行于B。也适用于顺序传递:如果A强先行于B,并且B强先行于C,那么A就肯定强先行于C。事件在线程间的先行关系与普通事件有所区别,这里的区别就在于操作被标记为memory_order_consume,但不是强先行关系。由于大多数代码并不适用memory_order_consume内存序,因此这种区别在实际中可能不会表现的很明显。

    5.3.3 原子操作的内存序

    这里有6个内存序列选项可应用于对原子类型的操作。

    1. memory_order_relaxed
    2. memory_order_consume
    3. memory_order_acquire
    4. memory_order_release
    5. memory_order_acq_rel
    6. memory_order_seq_cst

    除非为特定的操作指定一个序列选项,要不内存序列默认都是memory_order_seq_cst

    虽然有六个选项,但仅代表三种内存模型:顺序一致性(sequentially consistent),获取-释放序(memory_order_consumememory_order_acquirememory_order_releasememory_order_acq_rel)和自由序(memory_order_relaxed)。

    顺序一致性

    默认序命名为顺序一致性,因为程序中的行为从任意角度去看,序列都保持一定顺序。如果原子实例的所有操作都是序列一致的,那么多线程就会如单线程那样以某种特殊的排序执行。目前来看,该内存序是最容易理解的,这也是将其设置为默认的原因:不同的操作也要遵守相同的顺序。因为行为简单,可以使用原子变量进行编写。通过不同的线程,可以写出所有可能的操作消除那些不一致,以及确认代码的行为是否与预期相符。所以,操作都不能重排;如果代码在一个线程中,将一个操作放在另一个操作前面,那其他线程也需要了解这个顺序。

    从同步的角度看,是对同一变量的存储操作与载入操作的同步。这就提供了一种对两个(以上)线程操作的排序约束,但顺序一致的功能要比排序约束大的多,所以对于使用顺序一致的原子操作,都会存储值后再加载,代码5.4就是这种一致性约束的演示。这种约束不是线程在自由序中使用原子操作,这些线程依旧可以知道操作以不同顺序排列,所以必须使用顺序一致的操作去保证,多线程下的加速效果。

    //代码5.4 全序——序列一致性
    #include <atomic>
    #include <thread>
    #include <assert.h>
    
    std::atomic<bool> x,y;
    std::atomic<int> z;
    
    void write_x()
    {
      x.store(true,std::memory_order_seq_cst);  // 1
    }
    
    void write_y()
    {
      y.store(true,std::memory_order_seq_cst);  // 2
    }
    void read_x_then_y()
    {
      while(!x.load(std::memory_order_seq_cst));
      if(y.load(std::memory_order_seq_cst))  // 3
        ++z;
    }
    void read_y_then_x()
    {
      while(!y.load(std::memory_order_seq_cst));
      if(x.load(std::memory_order_seq_cst))  // 4
        ++z;
    }
    int main()
    {
      x=false;
      y=false;
      z=0;
      std::thread a(write_x);
      std::thread b(write_y);
      std::thread c(read_x_then_y);
      std::thread d(read_y_then_x);
      a.join();
      b.join();
      c.join();
      d.join();
      assert(z.load()!=0);  // 5
    }
    

    assert⑤语句是永远不会触发的,因为不是存储x的操作①发生,就是存储y的操作②发生。如果在read_x_then_y中加载y③返回false,是因为存储x的操作发生在存储y的操作之前。在read_y_then_x中加载x④必定会返回true,因为while循环能保证在某一时刻ytrue。因为memory_order_seq_cst的语义需要一个全序将所有操作都标记为memory_order_seq_cst,这就暗示着“加载y并返回false③”与“存储y①”的操作,需要有一个确定的顺序。只有在全序时,当一个线程看到x==true,随后又看到y==false,这就说明在总序列中存储x的操作发生在存储y的操作之前。

    因为事情都是对称的,所以有可能以其他方式发生,比如:加载x④的操作返回false,或强制加载y③的操作返回true。这两种情况下,z都等于1。当两个加载操作都返回truez就等于2。所以任何情况下,z都不能是0。

    read_x_then_y知道xtrue,并且yfalse时,这些操作就有“先行”关系了,如图5.3所示。

    在这里插入图片描述
    虚线始于read_x_then_y中对y的加载操作,到达write_y中对y的存储,其表示排序关系需要保持一致:全局操作序memory_order_seq_cst中,加载操作必须在存储操作前发生,这样就产生了图中的情况。

    非顺序一致性内存

    不同线程看到相同操作,不一定有着相同的顺序,还有对于不同线程的操作,都会一个接着另一个执行的想法就不可行了。不仅是考虑事情同时发生的问题,还有线程没办法保证一致性。为了写出(或仅是了解)一段使用非默认内存序列的代码,绝不仅是编译器重新排列指令的事情。即使线程运行相同的代码,都能拒绝遵循事件发生的顺序,因为操作在其他线程上没有明确的顺序限制,不同的CPU缓存和内部缓冲区,在同样的存储空间中可以存储不同的值。这非常重要,这里再重申一次:线程没办法保证一致性

    不仅是要摒弃串行的想法,还要放弃编译器或处理器重排指令的想法。没有明确顺序限制时,就需要所有线程要对每个独立变量统一修改顺序。对不同变量的操作可以体现在不同线程的不同序列上,提供的值要与任意附加顺序限制保持一致。

    踏出排序一致世界后,就使用memory_order_relaxed对所有操作进行约束。

    自由序

    原子类型上的操作以自由序执行。同一线程中对于同一变量的操作还是遵从先行关系,但不同线程不需要规定顺序。唯一的要求是在访问同一线程中的单个原子变量不能重排序,当给定线程看到原子变量的值时,随后线程的读操作就不会去检索较早的那个值。当使用memory_order_relaxed时,不需要任何额外的同步,对于每个变量的修改顺序只存在于线程间共享。

    //代码5.5 非限制操作只有非常少的顺序要求
    #include <atomic>
    #include <thread>
    #include <assert.h>
    
    std::atomic<bool> x,y;
    std::atomic<int> z;
    
    void write_x_then_y()
    {
      x.store(true,std::memory_order_relaxed);  // 1
      y.store(true,std::memory_order_relaxed);  // 2
    }
    void read_y_then_x()
    {
      while(!y.load(std::memory_order_relaxed));  // 3
      if(x.load(std::memory_order_relaxed))  // 4
        ++z;
    }
    int main()
    {
      x=false;
      y=false;
      z=0;
      std::thread a(write_x_then_y);
      std::thread b(read_y_then_x);
      a.join();
      b.join();
      assert(z.load()!=0);  // 5
    }
    

    这次assert⑤可能会触发,因为加载x的操作④可能读取到false,即使加载y的操作③读取到true,并且存储x的操作①先发与存储y的操作②。xy是两个不同的变量,所以没有顺序去保证每个操作产生相关值的可见性。

    非限制操作对于不同变量可以重排序,只要服从任意的先行关系即可(比如,在同一线程中)。尽管,不同的存储/加载操作间有着先行关系,这里不是在一对存储/加载之间了,所以加载操作可以看到“违反”顺序的存储操作。
    在这里插入图片描述

    获取-释放序

    这是自由序(relaxed ordering)的加强版,虽然操作依旧没有统一顺序,但引入了同步

    这种序列模型中,原子加载就是获取(acquire)操作(memory_order_acquire),原子存储就是释放(memory_order_release)操作,原子读-改-写操作(例如fetch_add()exchange())在这里,不是“获取”就是“释放”,或者两者兼有的操作(memory_order_acq_rel),同步在线程释放和获取间是成对的(pairwise),释放操作与获取操作同步就能读取已写入的值。下面列表中是使用获取-释放序(而非序列一致方式),对代码5.4的一次重写。

    //代码5.7 获取-释放不意味着统一操作顺序
    #include <atomic>
    #include <thread>
    #include <assert.h>
    
    std::atomic<bool> x,y;
    std::atomic<int> z;
    void write_x()
    {
      x.store(true,std::memory_order_release);
    }
    void write_y()
    {
      y.store(true,std::memory_order_release);
    }
    void read_x_then_y()
    {
      while(!x.load(std::memory_order_acquire));
      if(y.load(std::memory_order_acquire))  // 1
        ++z;
    }
    void read_y_then_x()
    {
      while(!y.load(std::memory_order_acquire));
      if(x.load(std::memory_order_acquire))  // 2
        ++z;
    }
    int main()
    {
      x=false;
      y=false;
      z=0;
      std::thread a(write_x);
      std::thread b(write_y);
      std::thread c(read_x_then_y);
      std::thread d(read_y_then_x);
      a.join();
      b.join();
      c.join();
      d.join();
      assert(z.load()!=0); // 3
    }
    

    例子中断言③可能会触发(就如同自由排序那样),因为在加载x②和y①时,可能读取到false。因为xy是由不同线程写入,所以序列中的每一次释放和获取都不会影响到其他线程的操作。

    图5.6展示了代码5.7的先行关系,对于读取的结果,两个(读取)线程看到的是两个完全不同的世界。如前所述,这可能是因为这里没有对在这里插入图片描述
    先行顺序进行强制规定导致的。

    为了了解获取-释放序的优点,需要考虑将两次存储由一个线程来完成,就像代码5.5那样。当需要使用memory_order_release改变y中的存储,并使用memory_order_acquire来加载y中的值,而后就会影响对x的操作。

    //代码5.8 获取-释放序操作会影响释放操作
    #include <atomic>
    #include <thread>
    #include <assert.h>
    
    std::atomic<bool> x,y;
    std::atomic<int> z;
    
    void write_x_then_y()
    {
      x.store(true,std::memory_order_relaxed);  // 1 
      y.store(true,std::memory_order_release);  // 2
    }
    void read_y_then_x()
    {
      while(!y.load(std::memory_order_acquire));  // 3 自旋,等待y被设置为true
      if(x.load(std::memory_order_relaxed))  // 4
        ++z;
    }
    int main()
    {
      x=false;
      y=false;
      z=0;
      std::thread a(write_x_then_y);
      std::thread b(read_y_then_x);
      a.join();
      b.join();
      assert(z.load()!=0);  // 5
    }
    

    最后,读取y③时会得到true,和存储时写入的一样②。存储使用的是memory_order_release,读取使用的是memory_order_acquire,存储与读取就同步了。因为这两个操作是由同一个线程串行完成的,所以存储x①的操作先行于存储y②的操作。对y的存储同步于对y的加载,存储x也就先行于对y的加载,并且扩展先行于x的读取。因此,加载x的值必为true,并且断言⑤不会触发。如果对于y的加载不是在while循环中,情况可能就会有所不同。加载y的时候可能会读取到false,这种情况下对于读取到的x是什么值没有要求了。为了保证同步,加载和释放操作必须成对。所以,无论有何影响,释放操作存储的值必须要让获取操作看到。当存储②或加载③都是一个释放操作时,对x的访问就无序了,也就无法保证④处读到的是true,并且还会触发断言。

    “线程间先行”的定义,一个很重要的特性就是可传递:当A线程间先行于B,并且B线程间先行于C,那么A就线程间先行于C。获取-释放序列可以在若干线程间使用同步数据,甚至在“中间”线程接触到这些数据前,使用这些数据

    获取-释放序传递同步

    为了考虑传递顺序,至少需要三个线程。第一个线程用来修改共享变量,第二个线程使用“加载-获取”读取由“存储-释放”操作过的变量,并且再对第二个变量进行“存储-释放”操作。最后,由第三个线程通过“加载-获取”读取第二个共享变量,并提供“加载-获取”操作来读取被“存储-释放”操作写入的值。为了保证同步关系,即便是中间线程没有对共享变量做任何操作,第三个线程也可以读取第一个线程操作过的变量。

    //代码5.9 使用获取和释放序传递同步
    std::atomic<int> data[5];
    std::atomic<bool> sync1(false),sync2(false);
    
    void thread_1()
    {
      data[0].store(42,std::memory_order_relaxed);
      data[1].store(97,std::memory_order_relaxed);
      data[2].store(17,std::memory_order_relaxed);
      data[3].store(-141,std::memory_order_relaxed);
      data[4].store(2003,std::memory_order_relaxed);
      sync1.store(true,std::memory_order_release);  // 1.设置sync1
    }
    
    void thread_2()
    {
      while(!sync1.load(std::memory_order_acquire));  // 2.直到sync1设置后,循环结束
      sync2.store(true,std::memory_order_release);  // 3.设置sync2
    }
    void thread_3()
    {
      while(!sync2.load(std::memory_order_acquire));   // 4.直到sync1设置后,循环结束
      assert(data[0].load(std::memory_order_relaxed)==42);
      assert(data[1].load(std::memory_order_relaxed)==97);
      assert(data[2].load(std::memory_order_relaxed)==17);
      assert(data[3].load(std::memory_order_relaxed)==-141);
      assert(data[4].load(std::memory_order_relaxed)==2003);
    }
    

    尽管thread_2只接触到变量syn1②和sync2③,对于thread_1thread_3的同步就足够了,这能保证断言不会触发。首先,thread_1将数据存储到data中先行于存储sync1①(它们在同一个线程内)。因为加载sync1①的是一个while循环,它最终会看到thread_1存储的值。因此,对于sync1的存储先行于最终对于sync1的加载(在while循环中)。thread_3的加载操作④,位于存储sync2③操作的前面。存储sync2③因此先行于thread_3的加载④,加载又先行于存储sync2③,存储sync2又先行于加载sync2④,加载syn2又先行于加载data。因此,thread_1存储数据到data的操作先行于thread_3中对data的加载,并且保证断言都不会触发。

    这个例子中,可将sync1sync2通过在thread_2中使用**“读-改-写”**操作(memory_order_acq_rel)合并成一个独立的变量。其中会使用compare_exchange_strong()来保证thread_1对变量只进行一次更新:

    std::atomic<int> sync(0);
    void thread_1()
    {
      // ...
      sync.store(1,std::memory_order_release);
    }
    
    void thread_2()
    {
      int expected=1;
      while(!sync.compare_exchange_strong(expected,2,
                  std::memory_order_acq_rel))
        expected=1;
    }
    void thread_3()
    {
      while(sync.load(std::memory_order_acquire)<2);
      // ...
    }
    

    使用**“读-改-写”**操作,选择语义就很重要了。例子中,想要同时进行获取和释放的语义,所以memory_order_acq_rel是一个不错的选择,但也可以使用其他内存序。即使存储了一个值,使用memory_order_acquire语义的fetch_sub不会和任何东西同步的,因为没有释放操作。同样,使用memory_order_release语义的fetch_or也不会和任何存储操作进行同步,因为对于fetch_or的读取,并不是一个获取操作。使用memory_order_acq_rel语义的“读-改-写”操作,每一个动作都包含获取和释放操作,所以可以和之前的存储操作进行同步,并且可以对随后的加载操作进行同步,就像上面例子一样。

    如果将获取-释放和序列一致进行混合,“序列一致”的加载动作就如使用了获取语义的加载操作,序列一致的存储操作就如使用了释放语义的存储,“序列一致”的读-改-写操作行为就如使用了获取和释放的操作。“自由操作”依旧那么自由,但其会和额外的同步进行绑定(也就是使用“获取-释放”的语义)。

    尽管结果并不那么直观,每个使用锁的同学都需要了解:锁住互斥量是一个获取操作,并且解锁这个互斥量是一个释放操作。随着互斥量的增多,必须确保同一个互斥量在读取变量或修改变量时上锁,所以获取和释放操作必须在同一个变量上,以保证访问顺序。当互斥量保护数据时,因为锁住与解锁的操作都是序列一致的操作,就保证了结果一致。当对原子变量使用获取和释放序时,代码必然会使用锁,即使内部操作序不一致,其外部表现将会为序列一致。

    当原子操作不需要严格的序列一致序时,可以提供成对同步的获取-释放序,这种比全局序列一致性的成本更低,且有同步操作。为了保证序列能够正常的工作,这里还需要一些权衡,还要保证隐式的跨线程行为是没有问题的。

    5.3.4 释放队列与同步

    通过线程在存储和加载操作之间有(有序的)多个“读-改-写”操作(所有操作都已经做了适当的标记),所以可以获取原子变量存储与加载的同步关系。存储操作标记为memory_order_releasememory_order_acq_relmemory_order_seq_cst,加载标记为memory_order_consummemory_order_acquirememory_order_sqy_cst,并且操作链上的每一加载操作都会读取之前操作写入的值,因此链上的操作构成了一个释放序列(release sequence),并且初始化存储同步(对应memory_order_acquirememory_order_seq_cst)或是前序依赖(对应memory_order_consume)的最终加载,操作链上的任何原子“读-改-写”操作可以拥有任意个内存序(甚至是memory_order_relaxed)。

    为了了解这些操作的作用和其重要性,这里假设使用atomic<int>对共享队列的元素进行计数:

    //代码5.11 使用原子操作从队列中读取数据
    #include <atomic>
    #include <thread>
    
    std::vector<int> queue_data;
    std::atomic<int> count;
    
    void populate_queue()
    {
      unsigned const number_of_items=20;
      queue_data.clear();
      for(unsigned i=0;i<number_of_items;++i)
      {
        queue_data.push_back(i);
      }
    
      count.store(number_of_items,std::memory_order_release);  // 1 初始化存储
    }
    
    void consume_queue_items()
    {
      while(true)
      {
        int item_index;
        if((item_index=count.fetch_sub(1,std::memory_order_acquire))<=0)  // 2 一个“读-改-写”操作
        {
          wait_for_more_items();  // 3 等待更多元素
          continue;
        }
        process(queue_data[item_index-1]);  // 4 安全读取queue_data
      }
    }
    
    int main()
    {
      std::thread a(populate_queue);
      std::thread b(consume_queue_items);
      std::thread c(consume_queue_items);
      a.join();
      b.join();
      c.join();
    }
    

    一种处理方式是让线程产生数据,并存储到一个共享内存中,而后调用count.store(number_of_items, memory_order_release)①让其他线程知道数据是可用的。线程群会消耗队列中的元素,调用count.fetch_sub(1, memory_order_acquire)②后,从队列中获取元素。在这之前,需要对共享内存进行完整的读取④。一旦count归零,队列中就没有元素了,这时线程必须等待③。

    只有一个消费者线程时还好,fetch_sub()带有memory_order_acquire的读取操作,并且存储操作是带有memory_order_release语义,所以存储与加载同步,线程可以从缓存中读取元素。当有两个读取线程时,第二个fetch_sub()操作将看到第一个线程修改的值,且没有值通过store写入其中。先不管释放序列的规则,第二个线程与第一个线程不存在先行关系,并且对共享内存中值的读取也不安全。除非第一个fetch_sub()是带有memory_order_release语义,为两个消费者线程建立了不必要的同步。无论是释放序列的规则,还是具有memory_order_release语义的fetch_sub操作,第二个消费者看到的是一个空的queue_data,无法从其获取任何数据,并且还会产生条件竞争。幸运的是,第一个fetch_sub()对释放顺序做了一些事情,所以store()能同步与第二个fetch_sub()操作。两个消费者线程间不需要同步关系。过程在图5.7中展示,其中虚线表示的就是释放顺序,实线表示的是先行关系。

    在这里插入图片描述
    操作链中可以有任意数量的链接,提供的都是“读-改-写”操作,比如fetch_sub()store(),每一个都会与使用memory_order_acquire语义的操作进行同步。例子中所有链接都是一样的,并且都是获取操作,但它们可由不同内存序列语义组成的操作混合。

    5.3.5 栅栏

    栅栏操作会对内存序列进行约束,使其无法对任何数据进行修改,典型的做法是与使用memory_order_relaxed约束序的原子操作一起使用。

    执行栅栏操作可以影响到在线程中的其他原子操作。因为这类操作就像画了一条任何代码都无法跨越的线一样,所以栅栏操作通常也被称为内存栅栏(memory barriers)

    //代码5.12 栅栏可以让自由操作变的有序
    #include <atomic>
    #include <thread>
    #include <assert.h>
    
    std::atomic<bool> x,y;
    std::atomic<int> z;
    
    void write_x_then_y()
    {
      x.store(true,std::memory_order_relaxed);  // 1
      std::atomic_thread_fence(std::memory_order_release);  // 2
      y.store(true,std::memory_order_relaxed);  // 3
    }
    
    void read_y_then_x()
    {
      while(!y.load(std::memory_order_relaxed));  // 4
      std::atomic_thread_fence(std::memory_order_acquire);  // 5
      if(x.load(std::memory_order_relaxed))  // 6
        ++z;
    }
    
    int main()
    {
      x=false;
      y=false;
      z=0;
      std::thread a(write_x_then_y);
      std::thread b(read_y_then_x);
      a.join();
      b.join();
      assert(z.load()!=0);  // 7
    }
    

    因为加载y的操作④读取③处存储的值,所以释放栅栏②与获取栅栏⑤同步。①处存储x先行于⑥处加载x,最后x读取出来必为true,并且不会触发断言⑦。原先不带栅栏的存储和加载x是无序的,并且断言是可能会触发。这两个栅栏都是必要的:需要在一个线程中进行释放,然后在另一个线程中进行获取,这样才能构建同步关系。

    例子中,如果存储y的操作③标记为memory_order_release,而非memory_order_relaxed,释放栅栏②也会对这个操作产生影响。同样,当加载y的操作④标记为memory_order_acquire时,获取栅栏⑤也会对之产生影响。使用栅栏的想法是:当获取操作能看到释放栅栏操作后的存储结果,那么这个栅栏就与获取操作同步。并且,当加载操作在获取栅栏操作前,看到一个释放操作的结果,那么这个释放操作同步于获取栅栏。当然,也可以使用双边栅栏操作。举一个简单的例子:当一个加载操作在获取栅栏前,看到一个值有存储操作写入,且这个存储操作发生在释放栅栏后,那么释放栅栏与获取栅栏同步。

    虽然,栅栏同步依赖于读取/写入的操作发生于栅栏之前/后,但是这里有一点很重要:同步点,就是栅栏本身。当执行代码5.12中的write_x_then_y,并且在栅栏操作之后对x进行写入,就像下面的代码一样。触发断言的条件就不保证一定为true了,尽管写入x的操作在写入y的操作之前发生。

    展开全文
  • 原子模型中如何定义孔隙呢,可以理解为给定一个点,最大的球能通过并且不和周围原子重叠。 STEP 1 那么先建立一个多孔结构 这是气体分布在多孔结构中(图片Ovito 生成),那么这个结构如何表征呢。 STEP 2 ...

    原子模型中如何定义孔隙呢,可以理解为给定一个点,最大的球能通过并且不和周围原子重叠

    在这里插入图片描述
    在这里插入图片描述

    STEP 1 那么先用·LAMMPS建立一个多孔结构

    这是气体分布在多孔结构中(图片Ovito 生成),那么这个结构如何表征呢。

    在这里插入图片描述

    计算结果

    展开全文
  • 知道今天看到CIE我才有一点感觉:隐约察觉到了数据结构和数据之间的区别。就像我们打开手机,我们不会说各式各样的框都是框,不会说各式各样的字都是一样的含义。我们在理解事物的时候会代入语境,会代入场景,会...
  • 由Java内存模型直接保证原子性的操作有load,assign,read,write,store,use这个6个,我们大致可以认为,基本数据类型的访问、读写都是具备原子性的(例外就是 long 和 double 的非原子性协定,各位只要知道这件...
  • JVM运行时内存结构回顾在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下整体结构如下所示,大致分为五大块而对于方法区中的数据,是属于所有线程共享的数据结构而对于虚拟机栈中数据结构,则是线程独有...
  • 网络版原子和分子结构可视化程序的开发以MATLAB为开发平台,编写一种基于网络的原子和分子结构可视化程序AMSW,有绘制主量子数小于9的任意类氢原(本文共5页)阅读全文>>随着交通、水利、市政等工程建设的高速发展...
  • 简介:在巍峨的数据库大厦体系中,查询优化器和事务体系是两堵重要的承重墙,二者是如此重要以至于整个数据库体系结构设计中大量的数据结构、机制和特性都是围绕着二者搭建起来的。他们一个负责如何更快的查询到数据...
  • 点击上方“小白学视觉”,选择加"星标"或“置顶” 重磅干货,第一时间送达 本文是小编结合了多个图模型的经典文章所作的一个总结,对于一谈到图模型和马尔科夫知识就产生厌恶的同...
  • Java内存模型(JMM-原子性、可见性、有序性)、volatile原理、 共享模型之内存 多线程的三大特性(原子性、可见性、有序性) 之前将的synchronize底层Monitor主要关注的是访问共享变量时,保证临界区代码的原子性...
  • 原子操作的实现原理总线锁定缓存锁定Java的原子操作实现循环时间开销大只能保证一个共享变量的原子操作Java内存模型Java内存模型的抽象结构指令重排序 原子操作的实现原理 处理器使用总线锁定和缓存锁定这两个机制来...
  • Java内存结构模型

    2021-03-17 18:34:35
    jvm的体系结构分为五个结构。方法区,堆,虚拟机栈,本地方法区,程序计数器PC。其中方法区和堆是所有线程共享的,每一个线程都有一个程序计数器a、寄存器:这个概念比较模棱两可,网上查了一下一种说法是Java中有...
  • 原子核外电子排布规则

    千次阅读 2020-12-31 13:45:20
    原子核外电子排布是原子核外电子的一种排列规则。原子核外电子排布与原子轨道的能级次序有关,排布市主要服从三个规则:能量Z低原理、泡利原理以及洪特规则。原子核外电子排布规则原子核外电子排布是有规律的,首先是...
  • 稀疏表示学习笔记(五) 本次主要学习资料是Duke大学Guillermo Sapiro教授的公开课——Image and video processing, 本节主要简要讲述结构化稀疏模型和高斯混合模型的思想。
  • 在我们之前的文章中,我们曾介绍过遗传算法在卷积模型结构搜索和Transformer结构搜索中的应用。如下:AutoML - 遗传算法与网络结构搜索Transformer + AutoML:...
  • 类的加载类的加载指的是将类的....类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口(下面的只是一个大概,后面说到jav...
  • 它屏蔽了底层架构的差异性,是 Java 跨平台的依据,也是每个 JavaJVM 体系结构Java Virtual Machine(JVM) 是一种抽象的计算机,基于堆栈架构,它有自己的指令集和内存如上所示,JVM 主要分为三个子系统:运行时和...
  • lammps教程:磨屑原子Ovito后处理教程

    千次阅读 2021-04-10 10:22:44
    本文以高熵合金磨削为例介绍如何使用Ovito进行磨屑原子的着色显示。 用到的命令有:select type、delete selected、Expression selection、Color coding。 主要步骤为: 1.删除磨粒球 将原子坐标文件拖入到Ovito...
  • 原子操作 即多线程程序中“最小的且不可并行化的”操作。通常对一个共享的资源的操作是原子操作的话,意味着多个线程访问资源时,有且仅有唯一一个线程在对这个资源进行操作。 通常情况下,原子操作都是通过互斥的...
  • 在巍峨的数据库大厦体系中,查询优化器和事务体系是两堵重要的承重墙,二者是如此重要以至于整个数据库体系结构设计中大量的数据结构、机制和特性都是围绕着二者搭建起来的。他们一个负责如何更快的查询到数据,更...
  • 来自浙江工业大学·网络空间安全研究院的宣琦教授团队的工作较早地探索了如何设计上的数据增强方法,研究表明,简单的启发式网络重连方法就可以作为有效的数据增强策略,对图模型的优化起到一定的帮助。...
  • jvm:java内存结构先看看多线程特性:1、原子性:保证数据一致性和安全性2、可见性:保证主线程中的共享变量进行修改后,本地线程能第一时间知道,这就是可见性,下面会配和代码进行说明3、有序性:jvm的一个重...
  • 一:补充知识 E-R向关系模式的转换需要考虑的是:...一个实体对应一个关系模型,实体的名称即是关系模型的名称,实体的属性就是关系模型的属性, 实体的码就是关系模型的码。 实体转换时需要注意的: 1:属性域的问题。 2:
  • 在制作框架结构或者其他稳定结构模型时,使用3dmax晶格指令可以让我们更方便地完成模型的建模。本期,就来和模型云一起看看使用3dmax晶格指令制作3dmax石墨烯模型的操作方法吧! 怎么使用3dmax晶格指令制作石墨烯...
  • 生成有真实世界中的的性质的 真实世界中图的性质: ThePowerLaw:真实世界中图的度分布多为幂律分布。即顶点的度是i的概率与成正比。检查是否符合ThePowerLaw的方法通常是在log-log坐标下以横坐标为度,纵...
  • 1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)对正点原子...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 72,200
精华内容 28,880
关键字:

原子结构模型图