atomic 原子自增工程用法案例

案例 1 : 簡單用法

atomic_int id;
atomic_fetch_add(&id, 1)
atomic_uint id;
atomic_fetch_add(&id, 1)

上面兩種原子自增簡單用法. 這裡面有潛在問題. 例如第一段程式碼中 atomic_fetch_add(&id, 1) 這種 id++

加到最後會溢出需要看業務能否接受了. 第二種死板一點, 也是一樣看業務取捨, 例如雜糅一些特殊業務值非常困難.

 

案例2: skynet 項目中 socket id 設計

//github.com/cloudwu/skynet/blob/master/skynet-src/socket_server.c#L345-L371

static int
reserve_id(struct socket_server *ss) {
    int i;
    for (i=0;i<MAX_SOCKET;i++) {
        int id = ATOM_FINC(&(ss->alloc_id))+1;
        if (id < 0) {
            id = ATOM_FAND(&(ss->alloc_id), 0x7fffffff) & 0x7fffffff;
        }
        struct socket *s = &ss->slot[HASH_ID(id)];
        int type_invalid = ATOM_LOAD(&s->type);
        if (type_invalid == SOCKET_TYPE_INVALID) {
            if (ATOM_CAS(&s->type, type_invalid, SOCKET_TYPE_RESERVE)) {
                s->id = id;
                s->protocol = PROTOCOL_UNKNOWN;
                // socket_server_udp_connect may inc s->udpconncting directly (from other thread, before new_fd), 
                // so reset it to 0 here rather than in new_fd.
                ATOM_INIT(&s->udpconnecting, 0);
                s->fd = -1;
                return id;
            } else {
                // retry
                --i;
            }
        }
    }
    return -1;
}

其中 ATOM_FINC, ATOM_FAND 設計如下

//github.com/cloudwu/skynet/blob/master/skynet-src/atomic.h 

#else

#include <stdatomic.h>

#define ATOM_INT atomic_int
#define ATOM_POINTER atomic_uintptr_t
#define ATOM_SIZET atomic_size_t
#define ATOM_ULONG atomic_ulong
#define ATOM_INIT(ref, v) atomic_init(ref, v)
#define ATOM_LOAD(ptr) atomic_load(ptr)
#define ATOM_STORE(ptr, v) atomic_store(ptr, v)
#define ATOM_CAS(ptr, oval, nval) atomic_compare_exchange_weak(ptr, &(oval), nval)
#define ATOM_CAS_POINTER(ptr, oval, nval) atomic_compare_exchange_weak(ptr, &(oval), nval)
#define ATOM_FINC(ptr) atomic_fetch_add(ptr, 1)
#define ATOM_FDEC(ptr) atomic_fetch_sub(ptr, 1)
#define ATOM_FADD(ptr,n) atomic_fetch_add(ptr, n)
#define ATOM_FSUB(ptr,n) atomic_fetch_sub(ptr, n)
#define ATOM_FAND(ptr,n) atomic_fetch_and(ptr, n)

#endif

他的思路是, 通過 atomic_fetch_and 來避免 atomic_fetch_add 溢出問題. 但也存在一個問題, 就是 atomic_fetch_and 存在返回相同值情況,

所以返回的 id 會重複.   這裡他藉助 atomic_compare_exchange_weak + 業務判斷 讓重複 id 再次重試. 

 

案例3: structc 中 id 設計

//github.com/wangzhione/structc/blob/master/modular/test/timer.c.h#L28-L56

// timer_list 鏈表對象管理器
struct timer_list {
    atomic_int id;            // 當前 timer node id
    atomic_flag lock;         // 自旋鎖
    volatile bool status;     // true is thread loop, false is stop
    struct timer_node * list; // timer list list
};

// 定時器管理單例對象
static struct timer_list timer = { .id = 1, .lock = ATOMIC_FLAG_INIT };

// get atomic int 1 -> INT_MAX -> 1
static inline int timer_list_id() {
    // 0 -> INT_MAX -> INT_MIN -> 0
    int id = atomic_fetch_add(&timer.id, 1) + 1;
    if (id < 0) {
        // INT_MAX + 1 -> INT_MIN
        // 0x7F FF FF FF + 1 -> 0x80 00 00 00

        // INT_MIN & INT_MAX => 0x80 00 00 00 & 0x7F FF FF FF => 0x00 00 00 00
        // id = atomic_fetch_and(&timer.id, INT_MAX) & INT_MAX;
        // Multiple operations atomic_fetch_and can ensure timer.id >= 0
        atomic_fetch_and(&timer.id, INT_MAX);

        // again can ensure id >= 1
        id = atomic_fetch_add(&timer.id, 1) + 1;
    }
    return id;
}

這個設計把 id 分為三段, -1, 0, 還有 > 0 . timer_list_id 函數返回 >= 1 id. 這裡利用二次 atomic_fetch_add 原子自增同時繼續保持不重複性.

 

後記:

技巧是為想法和需求服務, 歡迎補充更多原子操作用法.