Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
B
brpc
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
brpc
Commits
85d8fd80
Commit
85d8fd80
authored
Sep 04, 2017
by
gejun
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
polish table in atomic_instructions.md
parent
4a60b68b
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
26 additions
and
9 deletions
+26
-9
atomic_instructions.md
docs/atomic_instructions.md
+26
-9
No files found.
docs/
rpc_in_depth_
atomic_instructions.md
→
docs/atomic_instructions.md
View file @
85d8fd80
...
...
@@ -11,8 +11,6 @@
| x.compare_exchange_weak(expected_ref, desired) | 相比compare_exchange_strong可能有
[
spurious wakeup
](
http://en.wikipedia.org/wiki/Spurious_wakeup
)
。 |
| x.fetch_add(n), x.fetch_sub(n), x.fetch_xxx(n) | x += n, x-= n(或更多指令),返回修改之前的值。 |
你已经可以用这些指令做原子计数,比如多个线程同时累加一个原子变量,以统计这些线程对一些资源的操作次数。但是,这可能会有两个问题:
-
这个操作没有你想象地快。
...
...
@@ -33,10 +31,19 @@
仅靠原子累加实现不了对资源的访问控制,即使简单如
[
spinlock
](
https://en.wikipedia.org/wiki/Spinlock
)
或
[
引用计数
](
https://en.wikipedia.org/wiki/Reference_counting
)
,看上去正确的代码也可能会crash。这里的关键在于
**重排指令**
导致了读写一致性的变化。只要没有依赖,代码中在后面的指令(包括访存)就可能跑到前面去,
[
编译器
](
http://preshing.com/20120625/memory-ordering-at-compile-time/
)
和
[
CPU
](
https://en.wikipedia.org/wiki/Out-of-order_execution
)
都会这么做。这么做的动机非常自然,CPU要尽量塞满每个cycle,在单位时间内运行尽量多的指令。一个核心访问自己独有的cache是很快的,所以它能很好地管理好一致性问题。当软件依次写入a,b,c后,它能以a,b,c的顺序依次读到,哪怕在CPU层面是完全并发运行的。当代码只运行于单线程中时,重排对软件是透明的。但在多核环境中,这就不成立了。如上节中提到的,访存在等待cacheline同步时要花费数百纳秒,最高效地自然是同时同步多个cacheline,而不是一个个做。一个线程在代码中对多个变量的依次修改,可能会以不同的次序同步到另一个线程所在的核心上,CPU也许永远无法保证这个顺序如同TCP那样,有序修改有序读取,因为不同线程对数据的需求顺序是不同的,按需访问是合理的(从而导致同步cacheline的序和写序不同)。如果其中第一个变量扮演了开关的作用,控制对后续变量对应资源的访问。那么当这些变量被一起同步到其他核心时,更新顺序可能变了,第一个变量未必是第一个更新的,其他线程可能还认为它代表着其他变量有效,而去访问了已经被删除的资源,从而导致未定义的行为。比如下面的代码片段:
| 线程1 | 线程2 |
| ---------------------------------------- | ----------------------------------- |
|
`// ready was initialized to false``p.init();``ready = ``true``;`
|
`if`
`(ready) {`` ``p.bar();``}`
|
```
c++
// Thread 1
// ready was initialized to false
p
.
init
();
ready
=
true
;
```
```
c++
// Thread2
if
(
ready
)
{
p
.
bar
();
}
```
从人的角度,这是对的,因为线程2在ready为true时才会访问p,按线程1的逻辑,此时p应该初始化好了。但对多核机器而言,这段代码难以正常运行:
-
线程1中的ready = true可能会被编译器或cpu重排到p.init()之前,从而使线程2看到ready为true时,p仍然未初始化。
...
...
@@ -57,9 +64,19 @@
有了memory order,上面的例子可以这么更正:
| 线程1 | 线程2 |
| ---------------------------------------- | ---------------------------------------- |
|
`// ready was initialized to false``p.init();``ready.store(``true``, std::memory_order_release);`
|
`if`
`(ready.load(std::memory_order_acquire)) {`` ``p.bar();``}`
|
```
c++
// Thread1
// ready was initialized to false
p
.
init
();
ready
.
store
(
true
,
std
::
memory_order_release
);
```
```
c++
// Thread2
if
(
ready
.
load
(
std
::
memory_order_acquire
))
{
p
.
bar
();
}
```
线程2中的acquire和线程1的release配对,确保线程2在看到ready==true时能看到线程1 release之前所有的访存操作。
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment