在 C 語言中使用原子型別
Jinku Hu
2023年10月12日
本文將演示有關如何在 C 語言中使用原子型別的多種方法。
使用原子型別隱式同步對共享資源的訪問
原子型別的物件是唯一可以被多個執行緒同時訪問和修改而不會發生競爭條件的物件。這個特性對於從不同執行緒訪問的全域性變數和靜態變數至關重要,它會保留程式的正確性。通常,使用原子型別物件可以替代鎖定互斥物件之類的物件以及它們的標準 API 函式(如 mtx_lock
,mtx_unlock
等)。以下程式碼示例演示了計數問題的簡單情況,其中多個執行緒遞增一個共享的全域性計數器變數。最後,在程式末尾將總和列印到 stdout
。請注意,我們將 counter
宣告為通常的 int
型別。遺憾的是,即使某些執行可能會產生正確的結果,該程式還是有缺陷的。
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>
#include <unistd.h>
#ifndef NUM_THREADS
#define NUM_THREADS 4
#endif
int counter = 0;
enum { MAX_ITER = 10000 };
void incrementCounter(void *thr_id) {
long tid;
tid = (long)thr_id;
printf("thread %ld started incrementing ID - %lu\n", tid, thrd_current());
for (int i = 0; i < MAX_ITER; ++i) {
counter += 1;
}
thrd_exit(EXIT_SUCCESS);
}
int main(int argc, char const *argv[]) {
thrd_t threads[NUM_THREADS];
int rc;
long t;
for (t = 0; t < NUM_THREADS; t++) {
rc = thrd_create(&threads[t], (thrd_start_t)incrementCounter, (void *)t);
if (rc == thrd_error) {
printf("ERORR; thrd_create() call failed\n");
exit(EXIT_FAILURE);
}
}
for (t = 0; t < NUM_THREADS; t++) {
thrd_join(threads[t], NULL);
}
printf("count = %d\n", counter);
thrd_exit(EXIT_SUCCESS);
}
輸出:
thread 0 started incrementing ID - 140097636923136
thread 2 started incrementing ID - 140097620137728
thread 1 started incrementing ID - 140097628530432
thread 3 started incrementing ID - 140097611745024
count = 18851
請注意,使用 thrd_create
呼叫建立其他執行緒的主執行緒不會增加計數器變數,因此總和應為 MAX_ITER
常量和 NUM_THREADS
的倍數,表示執行緒數。可以通過用互斥鎖/解鎖功能或訊號量動作將 counter += 1
行包圍來解決此問題,但是在這種情況下,我們僅將 counter
宣告為 atomic_int
的型別。現在,這個整數物件具有原子屬性,這意味著對它的任何訪問都將作為一條指令進行而不會中斷,並保證了程式的順序執行。
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>
#include <unistd.h>
#ifndef NUM_THREADS
#define NUM_THREADS 4
#endif
atomic_int counter = 0;
enum { MAX_ITER = 10000 };
void incrementCounter(void *thr_id) {
long tid;
tid = (long)thr_id;
printf("thread %ld started incrementing ID - %lu\n", tid, thrd_current());
for (int i = 0; i < MAX_ITER; ++i) {
counter += 1;
}
thrd_exit(EXIT_SUCCESS);
}
int main(int argc, char const *argv[]) {
thrd_t threads[NUM_THREADS];
int rc;
long t;
for (t = 0; t < NUM_THREADS; t++) {
rc = thrd_create(&threads[t], (thrd_start_t)incrementCounter, (void *)t);
if (rc == thrd_error) {
printf("ERORR; thrd_create() call failed\n");
exit(EXIT_FAILURE);
}
}
for (t = 0; t < NUM_THREADS; t++) {
thrd_join(threads[t], NULL);
}
printf("count = %d\n", counter);
thrd_exit(EXIT_SUCCESS);
}
輸出:
thread 0 started incrementing ID - 140125987915520
thread 1 started incrementing ID - 140125979522816
thread 2 started incrementing ID - 140125971130112
thread 3 started incrementing ID - 140125962737408
count = 40000