日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

聊聊原子操作那些事

發布時間:2025/3/20 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 聊聊原子操作那些事 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

為什么80%的碼農都做不了架構師?>>> ??

原子操作,線程間交互數據最細粒度的同步操作,它可以保證線程間讀寫某個數值的原子性。

由于不需要加重量級的互斥鎖進行同步,因此非常輕量,而且也不需要在內核間來回切換調度,效率是非常高的。。

那如何使用原子操作了,各個平臺下都有相關api提供了支持,并且向gcc、clang這些編譯器,也提供了編譯器級的__builtin接口進行支持

  • windows的Interlockedxxx和Interlockedxxx64系列api
  • macosx的OSAtomicXXX系列api
  • gcc的__sync_val_compare_and_swap和__sync_val_compare_and_swap_8等__builtin接口
  • x86和x86_64架構的lock匯編指令
  • tbox的跨平臺原子接口
  • tbox接口使用

    先拿tbox的tb_atomic_fetch_and_add接口為例,顧名思義,這個api會先讀取原有數值,然后在其基礎上加上一個數值:

    // 相當于原子進行:b = *a++; tb_atomic_t a = 0; tb_long_t b = tb_atomic_fetch_and_add(&a, 1);

    如果需要先進行add計算,再返回結果可以用:

    // 相當于原子進行:b = ++*a; tb_atomic_t a = 0; tb_long_t b = tb_atomic_add_and_fetch(&a, 1);

    或者可以更加簡化為:

    tb_long_t b = tb_atomic_fetch_and_inc(&a); tb_long_t b = tb_atomic_inc_and_fetch(&a);

    那tbox在內部如何去適配各個平臺的呢,我們可以簡單看下,基本上就是對原生api進行了一層wrap而已。

    windows接口封裝

    static __tb_inline__ tb_long_t tb_atomic_fetch_and_add_windows(tb_atomic_t* a, tb_long_t v) {return (tb_long_t)InterlockedExchangeAdd((LONG __tb_volatile__*)a, v); } static __tb_inline__ tb_long_t tb_atomic_inc_and_fetch_windows(tb_atomic_t* a) {return (tb_long_t)InterlockedIncrement((LONG __tb_volatile__*)a); }

    gcc接口的封裝

    static __tb_inline__ tb_long_t tb_atomic_fetch_and_add_sync(tb_atomic_t* a, tb_long_t v) {return __sync_fetch_and_add(a, v); }

    x86和x86_64架構匯編實現

    static __tb_inline__ tb_long_t tb_atomic_fetch_and_add_x86(tb_atomic_t* a, tb_long_t v) {/** xaddl v, [a]:** o = [a]* [a] += v;* v = o;** cf, ef, of, sf, zf, pf... maybe changed*/__tb_asm__ __tb_volatile__ ( #if TB_CPU_BITSIZE == 64"lock xaddq %0, %1 \n" //!< xaddq v, [a] #else"lock xaddl %0, %1 \n" //!< xaddl v, [a] #endif: "+r" (v) : "m" (*a) : "cc", "memory");return v; }

    原子操作除了可以進行對int32和int64數值加減乘除外,還可以進行xor, or, and等邏輯計算,用法類似,這里就不多說了。

    下面我們再來個簡單的實例,來實際運用下,原子的應用場景還是蠻多的,比如:

    • 用于實現自旋鎖
    • 用于實現無鎖隊列
    • 線程間的狀態同步
    • 用于實現單例

    等等。。

    自旋鎖的實現

    我們先來看下如何去實現一個簡單的自旋鎖,為了統一規范演示代碼,下面的代碼都用tbox提供的原子接口為例:

    static __tb_inline_force__ tb_bool_t tb_spinlock_init(tb_spinlock_ref_t lock) {// init *lock = 0;// okreturn tb_true; } static __tb_inline_force__ tb_void_t tb_spinlock_exit(tb_spinlock_ref_t lock) {// exit *lock = 0; } static __tb_inline_force__ tb_void_t tb_spinlock_enter(tb_spinlock_ref_t lock) {/* 嘗試讀取lock的狀態值,如果還沒獲取到lock(狀態0),則獲取它(設置為1)* 如果對方線程已經獲取到lock(狀態1),那么循環等待嘗試重新獲取** 注:整個狀態讀取和設置,是原子的,無法被打斷*/tb_size_t tryn = 5;while (tb_atomic_fetch_and_pset((tb_atomic_t*)lock, 0, 1)){// 沒獲取到lock,嘗試5次后,還不成功,則讓出cpu切到其他線程運行,之后重新嘗試獲取if (!tryn--){// yieldtb_sched_yield();// reset tryntryn = 5;}} } static __tb_inline_force__ tb_void_t tb_spinlock_leave(tb_spinlock_ref_t lock) {// 釋放lock,此處無需原子,設置到一半被打斷,數值部位0,對方線程還是在等待中,不收影響*((tb_atomic_t*)lock) = 0; }

    這個實現非常簡單,但是tbox里面,基本上默認都是在使用這個spinlock,因為tbox里面大部分多線程實現,粒度都被拆的很細

    大部分情況下,用自旋鎖就ok了,無需進入內核態切換等待。。

    使用方式如下:

    // 獲取lock tb_spinlock_enter(&lock);// 一些同步操作 // ..// 釋放lock tb_spinlock_leave(&lock);

    上面的代碼中,省略了init和exit操作,實際使用時,在響應初始化和釋放的地方,做相應處理下就行了。。

    類pthread_once的實現

    pthread_once 可以在多線程函數內,可以保證傳入的函數只被調用到一次,一般可以用來初始化全局單例或者TLS的key初始化

    以tbox的接口為例,我先來來看下,這個函數的使用方式:

    // 初始化函數,只會被調用到一次 static tb_void_t tb_once_func(tb_cpointer_t priv) {// 初始化一些單例對象,全局變量// 或者執行一些初始化調用 }// 線程函數 static tb_int_t tb_thread_func(tb_cpointer_t priv) {// 全局存儲lock,并初始化為0static tb_atomic_t lock = 0;if (tb_thread_once(&lock, tb_once_func, "user data")){// ok} }

    我們這里拿原子操作,可以簡單模擬實現下這個函數:

    tb_bool_t tb_thread_once(tb_atomic_t* lock, tb_bool_t (*func)(tb_cpointer_t), tb_cpointer_t priv) {// checktb_check_return_val(lock && func, tb_false);/* 原子獲取lock的狀態** 0: func還沒有被調用* 1: 已經獲取到lock,func正在被其他線程調用中* 2: func已經被調用完成,并且func返回ok* -2: func已經被調用,并且func返回失敗failed*/tb_atomic_t called = tb_atomic_fetch_and_pset(lock, 0, 1);// func已經被其他線程調用過了?直接返回if (called && called != 1) {return called == 2;}// func還沒有被調用過?那么調用它else if (!called){// 調用函數tb_bool_t ok = func(priv);// 設置返回狀態tb_atomic_set(lock, ok? 2 : -1);// ok?return ok;}// 正在被其他線程獲取到lock,func正在被調用中,還沒完成?嘗試等待lockelse{// 此處簡單的做了些sleep循環等待,直到對方線程func執行完成tb_size_t tryn = 50;while ((1 == tb_atomic_get(lock)) && tryn--){// wait some timetb_msleep(100);}}/* 重新獲取lock的狀態,判斷是否成功* * 成功:2* 超時:1* 失敗:-2** 此處只要不是2,都算失敗*/return tb_atomic_get(lock) == 2; }

    64位原子操作

    64位操作跟32位的接口使用方式,是完全一樣的,僅僅只是變量類型的區別:

  • tbox中類型為tb_atomic64_t,接口改為tb_atomic64_xxxx
  • gcc中類型為volatile long long,接口改為__sync_xxxx_8系列
  • windows上則為Interlockedxxx64
  • 具體使用方式參考32位,這里就不詳細介紹了。。


    個人主頁:TBOOX開源工程

    轉載于:https://my.oschina.net/tboox/blog/754016

    總結

    以上是生活随笔為你收集整理的聊聊原子操作那些事的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。