原子變數的原理與應用

2021-08-08 07:38:03 字數 4533 閱讀 6885

當多個執行緒併發執行時,由於cpu隨時被搶占,程式的執行就會具有不可**性。cpu還需要處理各種外圍裝置的中斷。這種不可**性會變的更隨機。我們所期望的程式執行結果有可能會變得不可預期。

舉例說明:考慮生產者消費者的問題,我們有乙個變數counter,生產者增加conter的值,消費者減少counter的值。如下所示:

/*生產者操作p*/

…………

register1

=counter;

register1

=register1

+1;

counter

=register1

/*消費者操作c*/

…………

register2

=counter;

register2

=register2

-1;

counter

=register2

……

假如這段操作的,counter的初始值為5, 那麼最終的值可能是4、5、6.下表中是其中一次的執**況。我們可以採用此表演示出其他情況來。

執行緒執行語句結果p

register1 = counter;

register1 = 5

cregister2 = counter;

register2 = 5

pregister1 = register1+1;

register1 = 6

pcounter = register1

counter = 6

cregister2 = register2-1;

register2 = 4

ccounter = register2

counter = 4

為此,系統層面上需要提供一定的同步和互斥方式以解決上述問題。

在linux核心中,系統提供了多種方式的多執行緒的互斥方式,包括訊號量(semaphores)、互斥鎖(mutexes)、完成量(completions)、自旋鎖(spinlocks)、原子量(atomic variables)等,以上各種機制適合於不同的場景。

原子量是一種輕量級的互斥操作,他的實現原理是確保指定的變數在進行操作時,不能被訪問。因此,如果我們程式中所使用的互斥的資源只是乙個變數時,可以採用原子量確保系統的互斥。比如,我們需要乙個量來記錄在有多少個程序訪問某個檔案時,就可以採用乙個原子變數。

linux核心提供了現成的原子變數api可供使用:

介面說明

void atomic_set(atomic_t *v, int i);

將i的值賦值給變數v

int atomic_read(atomic_t *v);

返回變數v當前的值

void atomic_add(int i, atomic_t *v);

將i的值累加到原子變數v中

void atomic_sub(int i, atomic_t *v);

原子變數v中減掉i的值

void atomic_inc(atomic_t *v);

將變數v的值累加1

void atomic_dec(atomic_t *v);

將變數v的值減1

int atomic_inc_and_test(atomic_t *v);

int atomic_dec_and_test(atomic_t *v);

int atomic_sub_and_test(int i, atomic_t *v);

分析進行累加、減1、減少某個值後,判斷其值是否為0

int atomic_add_negative(int i, atomic_t *v);

累加之後判斷其值是否為負。如果為不為負的話返回0

int atomic_add_return(int i, atomic_t *v);

int atomic_sub_return(int i, atomic_t *v);

int atomic_inc_return(atomic_t *v);

int atomic_dec_return(atomic_t *v);

進行相應的操作之後,然後再返回最終儲存的值

在使用者空間,gcc從4.1.2提供了__sync_*系列的built-in函式,用於提供加減和邏輯運算的原子操作。

type

__sync_fetch_and_add (type

*ptr, type

value, ...)

type

__sync_fetch_and_sub (type

*ptr, type

value, ...)

type

__sync_fetch_and_or (type

*ptr, type

value, ...)

type

__sync_fetch_and_and (type

*ptr, type

value, ...)

type

__sync_fetch_and_xor (type

*ptr, type

value, ...)

type

__sync_fetch_and_nand (type

*ptr, type

value, ...)

type

__sync_add_and_fetch (type

*ptr, type

value, ...)

type

__sync_sub_and_fetch (type

*ptr, type

value, ...)

type

__sync_or_and_fetch (type

*ptr, type

value, ...)

type

__sync_and_and_fetch (type

*ptr, type

value, ...)

type

__sync_xor_and_fetch (type

*ptr, type

value, ...)

type

__sync_nand_and_fetch (type

*ptr, type

value, ...)

具體如何使用,可參考【

除此之外的,我們也可以將核心中實現的原子操作函式移植到使用者空間使用,當然也可重複造輪子,自己寫一套。

在軟體設計時,使用原子變數需要考慮從臨界區的問題。原子操作是將程式的臨界區縮小到乙個呼叫之內。可以理解為,對原子變數進行操作時,cpu之間是訪問是互斥的。原子變數上下的**都不在臨界區之外,因此原子操作上下的**段都不能保證其操作過程,沒有其他程序在執行與之相關聯的操作。這裡的相關聯指的是,對當前的**段結執行結果產生影響。

與此同時,在使用原子變數時,還要確保我們所定義的原子變數不被除上述所提供的原子變數操作函式之外的函式進行操作。

在實現的角度,上述的操作是與平台相關的。以x86平台為例,cpu從三個方面確保上述操作的原子性。

(1)確保相關操作在乙個cpu執行上是原子的,即操作不會被中斷,或者說訪存期間不會被打斷。所述的操作包括位元組、按16位對齊的字(word)、按32位對齊的雙字;新架構的cpu可以支援不對齊的訪問,但對效能有影響。

(2)在執行某些操作時,可以顯性的進行鎖前端匯流排,以使其他cpu不能夠進行訪存操作。

(3) 從p4以後的cpu,當顯性加鎖時,快取一致性協議能夠保證在快取中執行的操作,不被其他cpu訪問。這個操作進一步縮小了加鎖的範圍。使原來其他cpu不能夠訪問整個記憶體縮小到不充許訪問執行相關指令的cpu快取相關聯的記憶體。

當然為了確保系統的效能並且確保鎖機制不被隨意使用,cpu設計上還對加鎖的指令做了一定的限制,能夠顯性加鎖的指令是有限的,如bts、btr、btc、xadd、 cmpxchg等等。如果在使用非限定範圍的指令會造成cpu丟擲異常。

有了cpu的支援,我們實現多cpu間的原子操作就變的比較簡單了。只要在執行相應的指令之間加鎖就可以。

下面是原子加(atomic_add)實現的函式:

static

inline

void

atomic_add(int

i,atomic

*v)

最後,需要說明的是,除了原子操作之外,其他操作,比如自旋鎖、訊號量都需要通過原子操作儲存相關的資訊。

原子變數與原子操作

1.原子操作的速度要快於臨界區,event,互斥量,如果多個執行緒同時寫乙個變數時,最方便的就是原子操作。原子操作函式,解決多執行緒安全 2.原子變數也是為了解決執行緒衝突問題,如果兩個執行緒同時訪問同乙個變數,乙個執行緒改變了這個變數,另乙個執行緒就會出現一些bug。3.release和debug...

原子變數的操作

原子操作,顧名思義,就是說像原子一樣不可再細分。乙個操作是原子操作,意思就是說這個操作是以原子的方式被執行,要一口氣執行完,執行過程不能夠被os的其他行為打斷,是乙個整體的過程,在其執行過程中,os的其它行為是插不進來的。原子整數操作的使用 常見的用途是計數器,因為計數器是乙個很簡單的操作,所以無需...

GCC數值原子操作API原理及應用

c c 中數值操作,如自加 n 自減 n 及賦值 n 2 操作都不是原子操作,如果是多執行緒程式需要使用全域性計數器,程式就需要使用鎖或者互斥量,對於較高併發的程式,會造成一定的效能瓶頸。1.概要 為了提高賦值操作的效率,gcc提供了一組api,通過彙編級別的 來保證賦值類操作的原子性,相對於涉及到...