锁、无锁、多处理器编程——3.CAS实现

本文深入解析CAS(Compare and Swap)机制的工作原理,包括其解决的ABA问题、内存屏障的作用以及如何通过原子操作实现线程同步。通过具体实例展示了CAS在多处理器环境下的应用,对比了不同CPU架构下CAS与pthread锁的性能表现,强调了在实际场景中合理选择锁机制的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

CAS

Compare and Swap, 比较并交换。

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

ABA问题

ABA问题是CAS机制中出现的一个问题,就是说一个线程把数据A变为了B,然后又重新变成了A。此时另外一个线程读取的时候,发现A没有变化,就误以为是原来的那个A。

CAS实现

在github实现的CAS lock,利用内存屏障、原子变量、cmpxch指令集实现。
https://github.com/wangzhhbj/myCasLock

下面是提炼的关键地方:

//rte_atomic.h中

#define MPLOCKED        "lock ; "       /**< Insert MP lock prefix. */

static inline int
rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
{
    uint8_t res;

    asm volatile(
            MPLOCKED
            "cmpxchgl %[src], %[dst];"
            "sete %[res];"
            : [res] "=a" (res),     /* output */
              [dst] "=m" (*dst)
            : [src] "r" (src),      /* input */
              "a" (exp),
              "m" (*dst)
            : "memory");            /* no-clobber list */
    return res;
}
//lthread_mutex.h中
//锁结构体
struct lthread_mutex {
    volatile uint32_t owner;  /* 用pthread_self */
    //rte_atomic32_t    count; /* 原子计数*/
    char name[MAX_MUTEX_NAME_SIZE];   /* 锁名称 */
} __rte_cache_aligned;

/*
 * Try to obtain a mutex
 */
static int lthread_mutex_lock(struct lthread_mutex *m)
{
    uint32_t tid = pthread_self();

    if (m == NULL) {
        return POSIX_ERRNO(EINVAL);
    }

    /* allow no recursion */
    if (m->owner == tid) {
        return POSIX_ERRNO(EDEADLK);
    }

    for (;;) {
        do {
            if (rte_atomic32_cmpset
                ((uint32_t *) &m->owner, 0, tid)) {
                /* happy days, we got the lock */
                return 0;
            }
            /* spin due to race with unlock when
            * nothing was blocked
            */
        }while (m->owner == 0);
        
        sched_yield();
    }
    return 0;
}

/*
 * Unlock a mutex
 */
static int lthread_mutex_unlock(struct lthread_mutex *m)
{
    uint32_t tid = pthread_self();

    if (m == NULL) {
        return POSIX_ERRNO(EINVAL);
    }

    /* fail if its owned */
    if (m->owner != tid || m->owner == 0) {
        return POSIX_ERRNO(EPERM);
    }

    /* release the lock */
    m->owner = 0;
    
    return 0;
}

1、其中"lock ; "指令,是为了实现SMP下的内存屏障,保护后边的指令执行,避免出现ABA的问题。这个信号会使总线锁定,阻止其他处理器接管总线访问内存,直到使用LOCK前缀的指令执行结束,这会使这条指令的执行变为原子操作。在多处理器环境下,设置LOCK#信号能保证某个处理器对共享内存的独占使用。

2、在加锁时,如果owner为0,表示没有线程加锁,尝试使用CAS指令将owner赋值为当前线程id,失败则主动让出CPU,让其他线程任务执行,避免长时间占用CPU。

3、如果owner已经等于自身的线程id,则说明自己仍然持有该锁。

4、释放锁时,如果是自己加的锁,直接将owner赋值为0即可。

实现的CAS性能怎样

示例中有两个对比程序,在不同的CPU+内存场景下,尝试过intel,AMD下的ryzen架构的多个型号,有的型号实现的CAS耗时更低,有时pthread的lock耗时更低,CPU消耗更小。比如Ryzen3600下的虚拟机,自己的CAS耗时1.78秒,pthread耗时7.17秒。但是在E3-1230v3下,这两个执行情况完全相反。pthread耗时比自己实现的CAS小了2倍。

实际场景

实际开发场景下,并不建议直接使用CAS,绝大部分场景下pthread的lock并不会比cas性能低,使用cas需要做提前的实验论证,分析使用场景。

个人认为,更高的提高性能方式的方式:
1、减少lock的粒度。
2、设计更合理的数据结构。

参考资料

《多处理器编程的艺术》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值