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");
	}
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章