Built-in Functions for Memory Model Aware Atomic Operations

下面的内置函数大致符合C++ 11内存模型的要求。它们都是通过前缀'__atomic'来标识的,而且大多数都是重载的,因此它们可以处理多种类型。

这些函数用于替换传统的“__sync”内置函数。主要区别在于请求的内存顺序是函数的参数。新代码应该始终使用“__atomic”内置代码,而不是“__sync”内置代码。

注意,“__atomic”的构建假设程序将符合C++ 11内存模型。特别是,他们假设程序没有数据竞争。详见C++ 11标准。

“__atomic”内置项可用于长度为1、2、4或8字节的任何整数标量或指针类型。如果体系结构支持'__int128',则也允许16字节整数类型。

四个非算术函数(加载、存储、交换和比较交换)都有一个通用版本。此泛型版本适用于任何数据类型。如果特定的数据类型大小使其成为可能,则它使用无锁内置函数;否则,将在运行时解析外部调用。此外部调用的格式与添加“size_t”参数的格式相同,该参数作为第一个参数插入,指示要指向的对象的大小。所有对象的大小必须相同。

可以指定6种不同的内存顺序。这些映射到C++ 11的内存顺序具有相同的名称,参见C++ 11标准或GCC Wiki对原子同步的详细定义。单个目标还可以支持用于特定体系结构的额外内存顺序。有关详细信息,请参阅目标文档。

原子操作既可以约束代码运动,也可以映射到用于线程之间同步的硬件指令(例如,栅栏)。这种情况发生在什么程度上是由内存顺序控制的,这些顺序按大约升序顺序列出。每个内存顺序的描述仅仅是为了粗略地说明效果,而不是规范;参见C++ 11内存模型的精确语义。

函数 解释
__ATOMIC_RELAXED 意味着没有线程间排序约束。
__ATOMIC_CONSUME 由于C++1 11的语义对于memory_order_consume的不足,目前使用更强的__ATOMIC_ACQUIRE内存顺序来实现这一点。
__ATOMIC_ACQUIRE 在acquire加载处创建一个在多线程发生之前的约束。可防止操作前代码下沉。
__ATOMIC_RELEASE 将之前创建一个在多线程发生之前的约束在RELEASE处释放。可防止操作后代码下沉。
__ATOMIC_ACQ_REL 结合了__ATOMIC_ACQUIRE和__ATOMIC_RELEASE的效果。 
__ATOMIC_SEQ_CST 对所有其他原子顺序操作强制执行总排序。

请注意,在C++ 11内存模型中,栅栏(例如,'__atomic_thread_fence)与特定的内存位置(例如atomic load)上的其他原子操作结合起作用;在特定的内存位置上的操作不一定以相同的方式影响其他操作。

鼓励目标体系结构为每个原子内置函数提供自己的模式。如果没有提供目标,则使用原始的非内存模型集“__sync”原子内置函数,以及围绕它的任何必需的同步栅栏,以实现正确的行为。在这种情况下,执行受到与那些内置函数相同的限制。

如果没有提供无锁指令序列的模式或机制,则调用具有相同参数的外部例程以在运行时解析。

 在为这些内置函数实现模式时,只要该模式实现最严格的__ATOMIC_SEQ_CST内存顺序,就可以忽略memory order参数。任何其他的内存顺序都可以用这个内存顺序正确执行,但是它们可能没有用一个更合适的松弛需求实现来高效地执行。

注意,C++ 11标准允许在运行时而不是在编译时确定内存顺序参数。这些内置函数将任何运行时值映射到原子顺序cst,而不是调用运行时库调用或内联switch语句。这是标准的、安全的,也是目前最简单的方法。memory order参数是一个带符号的int,但是只有较低的16位保留给内存顺序。int的其余部分保留给目标使用,应设为0可确保预定义的原子值正确使用。

type __atomic_load_n(type *ptr,type val,int memorder):

此内置函数实现原子加载操作,它返回*ptr的内容。有效的存储顺序变量是__ATOMIC_RELAXED、__ATOMIC_SEQ_CST、__ATOMIC_ACQUIRE和__ATOMIC_COMSUME。

void __atomic_load(type *ptr,type*ret,int memorder):

这是原子加载的通用版本。它将*ptr的内容给了*ret。

void __atomic_store_n (type *ptr, type val, int memorder)

这个内置函数实现一个原子存储操作。它将val写入*ptr。有效的内存顺序变量是__ATOMIC_RELAXED、__ATOMIC_SEQ_CST、__ATOMIC_RELEASE。

void __atomic_store (type *ptr, type *val, int memorder)

这是原子存储的通用版本。它将*val的值存储到*ptr中。

type __atomic_exchange_n (type *ptr, type val, int memorder)

此内置函数实现原子交换操作。它将val写入*ptr,并返回*ptr以前的内容。有效的存储顺序变量是__ATOMIC_RELAXED、__ATOMIC_SEQ_CST、__ATOMIC_ACQUIRE、__ATOMIC_RELEASE和__ATOMIC_ACQ_REL。

void __atomic_exchange (type *ptr, type *val, type *ret, int memorder)

这是原子交换的通用版本它将*val的内容存储到*ptr中。*ptr的原始值被复制到*ret中。

bool __atomic_compare_exchange_n (type *ptr, type *expected, type desired, bool weak, int success_memorder, int failure_memorder)

此内置函数实现原子比较和交换操作。这会将*ptr的内容与*expected的内容进行比较。如果相等,则该操作是将desired写入*ptr的read-modify-write操作,如果它们不相等,则操作为read,*ptr的当前内容将写入*expected。weak为真时为weak compare_exchange,这可能是虚假的失败,为false时的strong variation,这从来没有虚假的失败。许多目标只提供强变化则可以忽略了参数。当有疑问时,使用strong variation。如果desired写入*ptr,则返回true,并且根据success_memorder指定的内存顺序影响内存。这里可以使用的内存顺序没有限制。

否则,根据failure_memorder返回False并影响内存。此内存顺序不能是__ATOMIC_RELEASE,也不能是__ATOMIC_ACQ_REL。它也不能是比success_memorder指定的顺序强的顺序。 

bool __atomic_compare_exchange (type *ptr, type *expected, type *desired, bool weak, int success memorder, int failure memorder)

此内置函数实现了原子比较交换的通用版本。该函数实际上与原子比较交换相同,只是所需的值也是一个指针。

type __atomic_add_fetch (type *ptr, type val, int memorder) 
type __atomic_sub_fetch (type *ptr, type val, int memorder) 
type __atomic_and_fetch (type *ptr, type val, int memorder)
type __atomic_xor_fetch (type *ptr, type val, int memorder)
type __atomic_or_fetch (type *ptr, type val, int memorder)
type __atomic_nand_fetch (type *ptr, type val, int memorder)

这些内置函数执行由名称建议的操作,并返回操作结果。对指针参数执行的操作就像操作数是uintptr_t类型一样,也就是说,它们不能更改指针的指向类型的大小

{ *ptr op= val; return *ptr; }
{ *ptr = ~(*ptr & val); return *ptr; } // nand

第一个参数指向的对象必须是整数或指针类型。它不能是布尔类型。所有内存顺序都有效。

type __atomic_fetch_add (type *ptr, type val, int memorder) 
type __atomic_fetch_sub (type *ptr, type val, int memorder)
type __atomic_fetch_and (type *ptr, type val, int memorder)
type __atomic_fetch_xor (type *ptr, type val, int memorder)
type __atomic_fetch_or (type *ptr, type val, int memorder)
type __atomic_fetch_nand (type *ptr, type val, int memorder)

这些内置函数执行名称建议的操作,并返回以前在*ptr中的值。对指针参数执行的操作就像操作数是uintptr_t类型一样,也就是说,它们不能更改指针的指向类型的大小

{ tmp = *ptr; *ptr op= val; return tmp; }
{ tmp = *ptr; *ptr = ~(*ptr & val); return tmp; } // nand

参数的约束与对应的__atomic_op_fetch内置函数的约束相同。所有内存顺序都有效。

bool __atomic_test_and_set (void *ptr, int memorder)

此内置函数对*ptr处的字节执行原子测试和设置操作。字节被设置为某个实现定义的非零“set”值,并且只有在前面的内容被“set”时,返回值才为true。它应该只用于bool或char类型的操作数。对于其他类型,只能设置部分值。所有内存顺序都有效。

void __atomic_clear (bool *ptr, int memorder)

这个内置函数对*ptr执行原子清除操作。操作后,*ptr包含0。它应仅用于bool或char类型的操作数,并与__atomic_test_and_set一起使用。对于其他类型,可能仅部分清除,如果类型不是bool,则更喜欢使用__atomic_store。有效的存储顺序变量是__ATOMIC_RELAXED, __ATOMIC_SEQ_CST, 和__ATOMIC_RELEASE。

void __atomic_thread_fence (int memorder)

此内置函数根据指定的内存顺序充当线程之间的同步栅栏。所有内存顺序都有效。

void __atomic_signal_fence (int memorder)

这个内置函数充当线程和基于同一线程的信号处理程序之间的同步栅栏。所有内存顺序都有效。

bool __atomic_always_lock_free (size t size, void *ptr)

如果字节大小的对象总是为目标体系结构生成无锁原子指令,则此内置函数返回true。大小必须解析为编译时常量,结果也解析为编译时常量。ptr是指向可用于确定对齐方式的对象的可选指针。值为0表示应使用典型的对齐方式编译器也可以忽略此参数。if (__atomic_always_lock_free (sizeof (long long), 0))

bool __atomic_is_lock_free (size t size, void *ptr)

如果字节大小的对象总是为目标体系结构生成无锁原子指令,则此内置函数返回true。如果不知道内置函数是无锁的,则调用名为“atomic”“is”“lock”“的运行时例程。ptr是指向可用于确定对齐方式的对象的可选指针。值为0表示应使用典型的对齐方式编译器也可以忽略此参数。

下面我们举几个例子说明一下

__atomic_load_n和__atomic_store_n的使用

#include<pthread.h>
#include<stdio.h>
#include <unistd.h>
#define ATOMIC_GET(x) __atomic_load_n(&(x), __ATOMIC_RELAXED)
#define ATOMIC_SET(x, y) __atomic_store_n(&(x), y, __ATOMIC_RELAXED)
void *thread1(void *a)
{
	int j;
	while(1)
	{
		j=ATOMIC_GET(*(int*)a);
		printf("thread1 %d\n",j);
	}
}
void *thread2(void *a)
{
	int k=0;
	while(1)
	{
		k++;
		ATOMIC_SET(*(int*)a,k);
		printf("thread2 %d\n",k);
	}
}
void main()
{
	int i=0;
	pthread_t id;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	if(pthread_create(&id,&attr,thread1,(void*)&i))
	{
		printf("wrong");
	}
	if(pthread_create(&id,&attr,thread2,(void*)&i))
	{
		printf("wrong");
	}
	while(1)
	{
		sleep(20);
		printf("main thread");
	}
}

__atomic_add_fetch和__atomic_fetch_add 

#include<pthread.h>
#include<stdio.h>
#include <unistd.h>
#define ATOMIC_PRE_INC(x) __atomic_add_fetch(&(x), 1, __ATOMIC_RELAXED)
#define ATOMIC_POST_INC(x) __atomic_fetch_add(&(x), 1, __ATOMIC_RELAXED)
void *thread1(void *a)
{
	int j,k;
	while(1)
	{
		j=ATOMIC_PRE_INC(*(int*)a);
		printf("PRE_INC %d,%d\n",*(int*)a,j);
		k=ATOMIC_POST_INC(*(int*)a);
		printf("POST_INC %d,%d\n",*(int*)a,k);
		sleep(5);
	}
}
void main()
{
	int i=0;
	pthread_t id;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	if(pthread_create(&id,&attr,thread1,(void*)&i))
	{
		printf("wrong");
	}
	while(1)
	{
		sleep(20);
		printf("main thread");
	}
}

我们发现__atomic_add_fetch会将ptr+val后返回,而__atomic_fetch_add会直接将ptr返回

__atomic_exchange_n

#include<pthread.h>
#include<stdio.h>
#include <unistd.h>
#define ATOMIC_CLEAR(x) __atomic_store_n(&(x), 0, __ATOMIC_RELAXED)
#define ATOMIC_XCHG(x, y) __atomic_exchange_n(&(x), y, __ATOMIC_RELAXED)
void *thread1(void *a)
{
	while(1)
	{
		ATOMIC_CLEAR(*((int*)a));
		ATOMIC_XCHG(*((int*)a+1),20);
		printf("%d %d,%d\n",*((int*)a),*((int*)a+1),*((int*)a+2));
		sleep(5);
	}
}
void main()
{
	int i[3]={5,6,7};
	printf("%d %d,%d\n",*(i+0),*(i+1),*(i+2));
	pthread_t id;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	if(pthread_create(&id,&attr,thread1,(void*)i))
	{
		printf("wrong");
	}
	while(1)
	{
		sleep(20);
		printf("main thread");
	}
}
发布了43 篇原创文章 · 获赞 23 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhang14916/article/details/102840354
今日推荐