• 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 设计

    https://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 设计如下

    https://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 再次重试. 

    这里还有个小彩蛋 if (ATOM_CAS(&s->type, type_invalid, SOCKET_TYPE_RESERVE))  使用 weak 版本而不是 strong 版本讨论

    https://github.com/cloudwu/skynet/discussions/1532

    业务上自己推荐 atomic_compare_exchange_strong 心里更稳一点. 我们再简单观察 glibc 和 gcc 中二者实现. 

    #define atomic_compare_exchange_strong_explicit(PTR, VAL, DES, SUC, FAIL) \
      __extension__                                \
      ({                                    \
        __auto_type __atomic_compare_exchange_ptr = (PTR);            \
        __typeof__ (*__atomic_compare_exchange_ptr) __atomic_compare_exchange_tmp \
          = (DES);                                \
        __atomic_compare_exchange (__atomic_compare_exchange_ptr, (VAL),    \
                       &__atomic_compare_exchange_tmp, 0,    \
                       (SUC), (FAIL));                \
      })
    
    #define atomic_compare_exchange_strong(PTR, VAL, DES)                \
      atomic_compare_exchange_strong_explicit (PTR, VAL, DES, __ATOMIC_SEQ_CST, \
                           __ATOMIC_SEQ_CST)
    
    #define atomic_compare_exchange_weak_explicit(PTR, VAL, DES, SUC, FAIL) \
      __extension__                                \
      ({                                    \
        __auto_type __atomic_compare_exchange_ptr = (PTR);            \
        __typeof__ (*__atomic_compare_exchange_ptr) __atomic_compare_exchange_tmp \
          = (DES);                                \
        __atomic_compare_exchange (__atomic_compare_exchange_ptr, (VAL),    \
                       &__atomic_compare_exchange_tmp, 1,    \
                       (SUC), (FAIL));                \
      })
    
    #define atomic_compare_exchange_weak(PTR, VAL, DES)            \
      atomic_compare_exchange_weak_explicit (PTR, VAL, DES, __ATOMIC_SEQ_CST, \
                         __ATOMIC_SEQ_CST)

    区别在于 __atomic_compare_exchange 的 第 4 个参数上面, 用 1 标识 weak 调用, 0 默认 strong 调用.
    例如 gcc 一种实现
    https://code.woboq.org/gcc/libgcc/config/tilepro/atomic.c.html#164 

    #define __atomic_exchange_methods(type, size) \
    bool
    _atomic_compare_exchange##size(volatile type* ptr, type* oldvalp,
    type newval, bool weak __unused,
    int models, int modelf __unused)

    weak 是 unused 未被使用的. 所以有些平台最终殊途同归, 类似 PF_INET 和 AF_INET 茴香豆几种写法.

    案例3: structc 中 id 设计

    https://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 分为三段, < 0 , 0, 还有 > 0 . timer_list_id 函数返回 >= 1 id. 这里利用二次 atomic_fetch_add 原子自增同时继续保持不重复性.

    后记:

    技巧多为想法和需求服务, 欢迎补充更多原子操作用法. 

  • 相关阅读:
    学习设计模式之中介者模式
    学习设计模式之责任链模式
    学习设计模式之命令模式
    学习设计模式之桥接模式
    学习设计模式之单例模式
    学习设计模式之迭代器模式
    Spring 源码学习——注册 BeanDefinition
    Html.DropDownListFor
    Home vs2013
    Jquery 操作页面中iframe自动跟随窗口大小变化,而页面不出现滚动条,只在iframe内部出滚动条
  • 原文地址:https://www.cnblogs.com/life2refuel/p/15871389.html
Copyright © 2020-2023  润新知