# Lock-Free Ringbuffer
在 box64 執行的過程中就已經使用了需多 lock ,如果測量期行為的程式也需使用 lock 的話無疑會讓執行過程更複雜,因此務必使用 lock-free
### Race Condition
大家都知道要避免 race condition, 但 race condition 會發生在哪些情境呢? 發生了會怎樣呢?
1. 多個生產者之間:
2. 生產者與消費者:
3. 多個消費者之間:
### lock-free
>於是我們需要變更原本執行緒的程式邏輯,當 atomic 操作不能成功時,就重來,直到 RMW 一類的 atomic 操作成功為止
並不是「沒有用到 mutex lock」 就是 lock-free, 而是「能保證至少有一個執行緒在進展」才叫 lock-free.
### A Pragmatic Implementation of Non-Blocking Linked-Lists
從論文中可以窺探到 lock-free 的秘密,即將步驟分成 `logical` 和 `physical` ,而 `logical` 會通知所有執行緒
#### Locks Aren't Slow; Lock Contention Is
- high contention
- high frequency
best-case and worst-case usage scenarios for locks ->
### lock-free ring buffer 的考量因素
#### Compiler Barrier
#### Memory Barrier
#### 執行路徑
#### ABA 問題
>「裡面放的東西一樣」不能確保「剛剛到現在都沒有人動過裡面的東西」
#### 多行程下的記憶體配置
### Memory Order
memory_order_relaxed
memory_order_consume
memory_order_acquire
memory_order_release
memory_order_acq_rel
memory_order_seq_cst
#### 延遲
---
### `ringbuf_shm`
> [ringbuf-shm](https://github.com/sysprog21/concurrent-programs/tree/master/ringbuf-shm)
考量因素
1. 跨行程的記憶體讀取
2. Bounded buffer
3. 共享記憶體的同步問題
4. Copy free
atomic + while + 預約 -> how ?
結構 :
```c
typedef struct {
uint32_t size, gap;
} ringbuf_element_t;
typedef struct {
size_t size, mask, rsvd /* reserved */, gapd;
memory_order acquire, release;
atomic_size_t head, tail;
uint8_t buf[] __attribute__((aligned(sizeof(ringbuf_element_t))));
} ringbuf_t;
```
#### 生產者
```c
static void *producer_main(void *arg)
{
ringbuf_t *ringbuf = arg;
uint64_t cnt = 0;
while (cnt < iterations) {
if (rand() < THRESHOLD)
nanosleep(&req, NULL);
size_t written = PAD(rand() * 1024.f / RAND_MAX);
size_t maximum;
uint8_t *ptr;
if ((ptr = ringbuf_write_request_max(ringbuf, written, &maximum))) {
assert(maximum >= written);
const uint8_t *end = ptr + written;
for (uint8_t *src = ptr; src < end; src += sizeof(uint64_t)) {
*(uint64_t *) src = cnt;
assert(*(uint64_t *) src == cnt);
}
ringbuf_write_advance(ringbuf, written);
cnt++;
} /* else: buffer full */
}
return NULL;
}
```
### `MPSC`
> [mpsc](https://github.com/sysprog21/concurrent-programs/blob/master/mpsc/mpsc.c)
測試程式:
1. 是否符合 FIFO
考量因素:
1. 多個生產者同時寫入
2. ABA 問題
### 結構
```c
typedef struct node {
atomic_uintptr_t next;
} node;
struct queue_internal {
atomic_uintptr_t head, tail;
size_t item_size;
};
```
什麼意思?這是 linked-list 的意思嗎?其實不然
#### 生產者
生產者會更新執行的次數 `producer_count` 並將 `in` push 到 Queue 中,然而 `test_producer` 是如何做到 lock-free 呢? 如何確保對共享 queue 的讀-修改-寫(read-modify-write)沒有被打擾呢?
```c
static void *test_producer(void *arg)
{
queue_test_t *test = (queue_test_t *) arg;
assert(test->q);
while (1) {
int in = atomic_fetch_add(&test->producer_count, 1);
if (in >= THREAD_COUNT)
break;
queue_result_t result = Queue.push(test->q, &in);
assert(result == QUEUE_SUCCESS);
}
return NULL;
}
```
`THREAD_COUNT` 的用意為何? 跟 `PRODUCER_COUNT` 的差別是?
> `THREAD_COUNT` 是指執行過程中所用到的執行緒數量上限,即 `consume_count`(消費者執行的次數) + `producer_count` (生產者執行的次數) <= `THREAD_COUNT`
```c
static queue_result_t queue_push(queue_p q, void *data)
{
assert(q);
/* create the new tail */
node *new_tail = malloc(sizeof(node) + q->item_size);
if (!new_tail)
return QUEUE_OUT_OF_MEMORY;
atomic_init(&new_tail->next, 0);
memcpy(new_tail + 1, data, q->item_size);
/* swap the new tail with the old */
node *old_tail =
(node *) atomic_exchange(&q->tail, (atomic_uintptr_t) new_tail);
/* link the old tail to the new */
atomic_store(old_tail ? &old_tail->next : &q->head,
(atomic_uintptr_t) new_tail);
return QUEUE_SUCCESS;
}
```
可以注意到 psuh 分成兩個步驟:
a. 找到要「接上」的 node (相當於對所有執行緒預告了未來的 tail)
b. 「接上」node
>那如果失敗呢?
步驟 a 不一定要直接接著步驟 a ,兩步驟間可以穿插其他執行緒的步驟,假設有兩個生產者的執行緒同時要加入節點
| atomic_exchange | 成功 | 失敗|
|----|----|----|
|`old_tail`||
|`q->tail`||
| atomic_store | 成功 | 失敗|
|----|----|----|
|`old_tail->next`||
|`q->head`||
####
### 參考
1. [lock-free](https://github.com/sadhbh-c0d3/lock-free)
2. [並行程式設計: Atomics 操作](https://hackmd.io/@sysprog/concurrency/%2F%40sysprog%2Fconcurrency-atomics#wait-free-amp-lock-free)
3. [Linux 核心設計: 淺談同步機制](https://hackmd.io/@sysprog/linux-sync#spinlock)
4. [7.17 Atomics <stdatomic.h>](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf)
5. [5.1.2.4 Multi-threaded executions and data races](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf)