---
# System prepended metadata

title: 〈Concurrency Primer〉校訂和範例撰寫
tags: [linux kernel]

---

---
tags: linux kernel
---

# 〈Concurrency Primer〉校訂和範例撰寫
Contributed by < `huang-me` >

## 使用 atomic 與否差異
```c
#include <stdatomic.h>

int v = 0;
atomic_bool v_ready = false;
int bv;

void *threadA() {
	v = 42;
	v_ready = true;
}

void *threadB() {
	while(!v_ready);
	bv = v;
}
```
:::warning
以下使用 gcc -O3 optimization
:::
- threadA 中： 若將 `v_ready` 定義為 amtomic_bool 則 compiler 將會插入 memory fence 使得所有在之前寫入的 register 值都被更新至 memory 中。
    ```diff
    threadA:				threadA:
    .LFB40:					.LFB40:
        .cfi_startproc				.cfi_startproc
        endbr64					endbr64
        movl	$42, v(%rip)			movl	$42, v(%rip)
        movb	$1, v_ready(%rip)		movb	$1, v_ready(%rip)
        mfence			     <
        ret					ret
    ```
- threadB 中：若不是使用 `atomic_bool`，compiler 會對 while loop 中的讀取進行優化，造成無限迴圈而無法正常執行。(row 7,8)
```diff=
threadB:				threadB:
.LFB41:					.LFB41:
	.cfi_startproc				.cfi_startproc
	endbr64					endbr64
				     >		cmpb	$0, v_ready(%rip)
				     >		jne	.L4
				     >	.L5:
				     >		jmp	.L5
	.p2align 4,,10				.p2align 4,,10
	.p2align 3				.p2align 3
.L4:					.L4:
	movzbl	v_ready(%rip), %eax  <
	testb	%al, %al	     <
	je	.L4		     <
	movl	v(%rip), %eax			movl	v(%rip), %eax
	movl	%eax, bv(%rip)			movl	%eax, bv(%rip)
	ret					ret
```

## Check if atomic element is lock free
C/C++ 中分別定義`atomic_is_lock_free`/`std::atomic<T>::is_lock_free` 可用以檢驗此 atomic 變數是否可以在 lock free 的情況下做到 atomic operation。
```c
struct foo {
	int a, b, c;
	char *s;
};

atomic_bool v_ready = false;
struct foo f;

int main() {
	printf("foo is lock free? %s\n", atomic_is_lock_free(&f) ? "true" : "false");
	printf("v_ready is lock free? %s\n", atomic_is_lock_free(&v_ready) ? "true" : "false");
	return 0;
}
```
:::success
以上測試程式執行結果如下：
```shell
foo is lock free? false
v_ready is lock free? true
```
- 若定義的 atomic variable 大小大於一個 register 之大小且 variable **aligned**，則需要使用 mutex lock 確保 operation 之 atomicity。
- 反之，若 variable 沒有被 aligned ，則是否為 lock free 取決於硬體架構的設計方式。
:::

## Read-modify-write
當需要 atomically 完成讀取、修改、寫入的一系列指令時，可以使用以下指令
- `atomic_<type>`: \
    `atomic_compare_exchange` & `atomic_exchange`
- `atomic_flag`: \
    `atomic_flag_test_and_set` & `atomic_flag_clear`
### 利用 `atomic_flag` 實作 mutex lock
```c
atomic_flag f = ATOMIC_FLAG_INIT;
void atomic_operation() {
    while(atomic_flag_test_and_set(&f))
        nop;
    /*
        critical section
    */
    atomic_flag_clear(&f);
    return;
}
```
### `atomic_flag` 編譯結果
- 原始 c code：
    ```c
    void *threadA() {
        while(atomic_flag_test_and_set(&f));
        t1.a++;
        atomic_flag_clear(&f);
        return NULL;
    }
    ```
- 對應 assembly
    ```
    .L2:
        movl	%edx, %eax
        xchgb	f(%rip), %al
        testb	%al, %al
        jne	.L2
        addl	$1, t1(%rip)
        xorl	%eax, %eax
        movb	$0, f(%rip)
        mfence
        ret
    ```
:::success
`atomic_flag_test_and_set` 使用 x86 的 exchange 指令 `xchgb` 設定 flag 的值 \
`atomic_flag_clear` 則在寫入值之後還加上了 memory fence 以確保所有值的修改都可以被其他 processor 看見。
:::

### compare and exchange
- CAS c code
    ```c
    atomic_int ai = 0;
    void cas() {
        atomic_int exp = 0;
        atomic_compare_exchange_strong(&ai, &exp, 1);
        return;
    }
    ```
- 對應 assembly code
    ```=
    cas:
    .LFB24:
        .cfi_startproc
        endbr64
        xorl	%eax, %eax
        movl	$1, %edx
        lock cmpxchgl	%edx, ai(%rip)
        ret
    ```
:::success
第 7 行中，`lock` 使得在做 `cmpxchgl` 時同時只能有一個 cpu 獲得此變數的存取權。\
並且 `cmpxchgl` 先比較 `%eax` 以及 destination register (`ai(%rip`)，值相等時才將 `%edx` 中的值與 `ai(%rip)` 互換。
:::

### load-link and store-conditional (LL/SC)
在某些 cpu 架構之下(MIPS,RISC-V,ARM,etc...)，有指令可以追蹤此次要 store 的目標值是否被修改過，以避免 load modfiy store 覆蓋已經被更新的值。
### `atomic_compare_exchange_weak` 存在意義
```c
void addone(int val) {
    int expected = val;
    while(!atomic_compare_exchange_?(&val, &expected, val + 1))
        nop;
    return;
}
```
我們可能經常使用以上方式進行 CAS 動作，但是如果硬體有支援 ll/sc 的話則有可能錯誤發生在 sc 而非一開始比較 `val` 以及 `expected` 時，但其時我們不在乎什麼時候出現錯誤，所以定義 weak 版本的 CAS 只要動作沒有正確的完成就會回傳 true。

## Memory ordering
有時候我們並不需要所有的指令都在一定的順序下執行，有部分的指令可以提前或者延後執行時間。\
因此，在 `stdatomic.h` 中有 6 種 memory ordering model 可以讓 compiler 清楚各指令可以接受的優化方向。
### Sequentially consistent
在各個 thread 中，所有的指定為 `memory_order_seq_cst` 指令都是 sequentially 執行，所有的執行順序跟程式被撰寫的順序相同，因此沒有重排指令造成執行結果錯誤的可能性。
```c
void *threadFunc() {
	atomic_int val = atomic_load_explicit(&a, memory_order_seq_cst);
	val += 1;
	atomic_store_explicit(&a, val, memory_order_seq_cst);
	return NULL;
}
```
> 若同時讓多個 thread 執行以上 function，指令的執行順序按照 thread 建立時間以及指令順序執行。
:::danger
使用 sequentially consistent 的缺點為即使使用多個 thread 還是按照 sequential 執行，使得執行效率低落，甚至可能跟 single thread 時差不多，但卻浪費了很多資源。
:::
### Acquire
只能用在 `atomic_load`\
所有在此 load 之後的指令都不能被重排到此指令的前面。
### Release
只能用在 `atomic_store`\
與 Acquire 相反，所有在此 store 之前的指令都不能被重排到此指令的後面。
### Acquire Release
可以用在 `atomic_load` 以及 `atomic_store`\
必須同時滿足 Acquire 以及 Release 條件
### Relaxed
所有 atomic operation 都 atomically 執行，但是不同 thread 之間對於相同變數的執行順序沒有固定。
### Consume
基本上跟 `Acquire` 相似，不同點在於只需要保證相同變數的順序，其他不相關的變數可以做前後順序的調換，且 memory barrier 的加入為非必要。

:::danger
〈Atomic Primer〉勘誤
- Enforcing law and order:\
    ![](https://i.imgur.com/Rbc92DH.png)
    > 應修正為: \
    "They look and act just like the type they mirror ..."
:::