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、设计更合理的数据结构。
参考资料
《多处理器编程的艺术》