什么是原子操作
- 原子操作指的是对原子对象的读和写是不可被打断的。一个操作不可被打断意味着在执行整个操作过程中,即便有一个硬件中断信号过来,该中断信号也不能立即触发处理器的中断执行例程,处理器必须执行完整条原子操作之后才可进入中断执行例程。
- 对于来自多个处理器核心对同一个存储空间的访问,存储器控制器会去仲裁当前哪个原子操作先进行访存操作,哪个后进行,这些访存操作都会被串行化。
所以原子对象往往用于多核多线程并行计算中对多个线程共享变量的计算。
什么是原子类型
用_Atomic(类型名)这种方式修饰的类型是原子类型,在实际使用原子类型时应当避免直接使用_Atomic(类型名)这种形式,而是直接用<stdatomic.h>头文件中已经定义好的原子类型。此外该头文件还有相应的原子操作函数。
常用的原子类型
typedef _Atomic _Bool atomic_bool;
typedef _Atomic char atomic_char;
typedef _Atomic signed char atomic_schar;
typedef _Atomic unsigned char atomic_uchar;
typedef _Atomic short atomic_short;
typedef _Atomic unsigned short atomic_ushort;
typedef _Atomic int atomic_int;
typedef _Atomic unsigned int atomic_uint;
typedef _Atomic long atomic_long;
typedef _Atomic unsigned long atomic_ulong;
typedef _Atomic long long atomic_llong;
typedef _Atomic unsigned long long atomic_ullong;
...
常用的原子操作函数
atomic_init 初始化
atomic_store 赋值
atomic_load 获取
atomic_fetch_add 加
atomic_fetch_sub 减
...
单线程示例
#include <stdio.h>
#include <stdatomic.h>
atomic_int atomic_count = ATOMIC_VAR_INIT(1);
void test()
{
atomic_int a;
atomic_init(&a,10);
int* a_ptr = (int *) &a;
atomic_store(a_ptr,20);
int b = atomic_load(a_ptr);
printf("b = %d\n",b);
int* atomic_count_ptr = (int *) &atomic_count;
int atomic_count_old_value = atomic_fetch_add(atomic_count_ptr,b);
printf("atomic_count_old_value = %d\n",atomic_count_old_value);
int int_atomic_count = atomic_load(atomic_count_ptr);
printf("atomic_count = %d\n",int_atomic_count);
}
int main(void)
{
test();
return 0;
}
多线程示例
#include <stdio.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <pthread.h>
static volatile atomic_ullong sAtomResult = ATOMIC_VAR_INIT(0);
static volatile atomic_int sAtomIndex = ATOMIC_VAR_INIT(0);
static volatile uint64_t sNormalResult = 0;
static volatile int sNormalIndex = 0;
static volatile bool sIsThreadComplete = false;
static int sArray[10000][100];
static void* NormalSumProc(void *param) {
int currIndex;
while((currIndex = sNormalIndex++) < 10000) {
uint64_t sum = 0;
for (int i = 0;i < 100; i++) {
sum += sArray[currIndex][i];
}
sNormalResult += sum;
}
sIsThreadComplete = true;
return NULL;
}
static void* AtomSumProc(void *param) {
int currIndex;
while((currIndex = atomic_fetch_add(&sAtomIndex,1))<10000) {
uint64_t sum = 0;
for (int i = 0; i < 100; ++i) {
sum += sArray[currIndex][i];
}
atomic_fetch_add(&sAtomResult,sum);
}
sIsThreadComplete = true;
return NULL;
}
int main(int argc,const char* argv[]){
for (int i = 0; i < 10000; ++i) {
for (int j = 0; j < 100; ++j) {
sArray[i][j] = 100 * i + j;
}
}
unsigned long long standardResult = 0;
for (int i = 0; i < 10000; ++i) {
for (int j = 0; j < 100; ++j) {
standardResult += sArray[i][j];
}
}
printf("The standard result is %llu\n",standardResult);
pthread_t pthreadID;
pthread_t threadID;
pthread_create(&threadID,NULL,&NormalSumProc,NULL);
int currIndex;
while((currIndex = sNormalIndex++)<10000){
uint64_t sum = 0;
for (int i = 0; i < 100; ++i) {
sum += sArray[currIndex][i];
}
sNormalResult += sum;
}
while(!sIsThreadComplete);
if (sNormalResult == standardResult) {
puts("Normal compute compared equal!");
} else {
printf("Normal compute compared not equal: %lu\n",sNormalResult);
}
sIsThreadComplete = false;
pthread_create(&threadID,NULL,&AtomSumProc,NULL);
while((currIndex = atomic_fetch_add(&sAtomIndex,1))<10000){
uint64_t sum = 0;
for (int i = 0; i < 100; ++i) {
sum += sArray[currIndex][i];
}
atomic_fetch_add(&sAtomResult,sum);
}
while(!sIsThreadComplete);
if (atomic_load(&sAtomResult) == standardResult) {
puts("Atom compute compared equal!");
} else {
puts("Atom compute compared not equal!");
}
}
代码设计不再多说。
The standard result is 499999500000
Normal compute compared not equal: 498547236350
Atom compute compared equal!
注意:修改CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(CAtomic C)
set(CMAKE_C_STANDARD 11)
SET(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-pthread")
add_executable(CAtomic main.c)
参考
C语言编程魔法书
网友评论