DPDK 锁:ticketlock和mcslock

RToax 2021年4月

1. spinlock

/*** The rte_spinlock_t type.*/
typedef struct {volatile int locked; /**< lock status 0 = unlocked, 1 = locked */
} rte_spinlock_t;

其中volatile标识易失的,表明每次读取locked变量时需要直接从内存中读取,这虽然减少了缓存震荡带来的问题,但一次内存IO的开销也是严重的。

使用__atomic_xxx实现,此处的宏定义的意思为:

  • __ATOMIC_RELAXED //编译器和处理器可以对memory access做任何的reorder
  • __ATOMIC_ACQUIRE //保证本线程中,所有后续的读操作必须在本条原子操作完成后执行。
  • __ATOMIC_RELEASE //保证本线程中,所有之前的写操作完成后才能执行本条原子操作。
  • __ATOMIC_ACQ_REL //同时包含 memory_order_acquire 和 memory_order_release
  • __ATOMIC_CONSUME //释放消费顺序(release/consume)的规范正在修订中,而且暂时不鼓励使用
  • __ATOMIC_SEQ_CST //最强的同步模式,同时也是默认的模型,具有强烈的happens-before语义

在下面的

static inline void
rte_spinlock_lock(rte_spinlock_t *sl)
{int exp = 0;while (!__atomic_compare_exchange_n(&sl->locked, &exp, 1, 0,__ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {while (__atomic_load_n(&sl->locked, __ATOMIC_RELAXED))rte_pause();exp = 0;}
}
static inline void
rte_spinlock_unlock (rte_spinlock_t *sl)
{__atomic_store_n(&sl->locked, 0, __ATOMIC_RELEASE);
}
static inline int
rte_spinlock_trylock (rte_spinlock_t *sl)
{int exp = 0;return __atomic_compare_exchange_n(&sl->locked, &exp, 1,0, /* disallow spurious failure */__ATOMIC_ACQUIRE, __ATOMIC_RELAXED);
}
static inline int rte_spinlock_is_locked (rte_spinlock_t *sl)
{return __atomic_load_n(&sl->locked, __ATOMIC_ACQUIRE);
}

使用__sync_xxx实现

static inline void
rte_spinlock_lock(rte_spinlock_t *sl)
{while (__sync_lock_test_and_set(&sl->locked, 1))while (sl->locked)rte_pause();
}static inline void
rte_spinlock_unlock(rte_spinlock_t *sl)
{__sync_lock_release(&sl->locked);
}static inline int
rte_spinlock_trylock(rte_spinlock_t *sl)
{return __sync_lock_test_and_set(&sl->locked, 1) == 0;
}

使用asm实现:

static inline void
rte_spinlock_lock(rte_spinlock_t *sl)
{int lock_val = 1;asm volatile ("1:\n""xchg %[locked], %[lv]\n""test %[lv], %[lv]\n""jz 3f\n""2:\n""pause\n""cmpl $0, %[locked]\n""jnz 2b\n""jmp 1b\n""3:\n": [locked] "=m" (sl->locked), [lv] "=q" (lock_val): "[lv]" (lock_val): "memory");
}static inline void
rte_spinlock_unlock (rte_spinlock_t *sl)
{int unlock_val = 0;asm volatile ("xchg %[locked], %[ulv]\n": [locked] "=m" (sl->locked), [ulv] "=q" (unlock_val): "[ulv]" (unlock_val): "memory");
}static inline int
rte_spinlock_trylock (rte_spinlock_t *sl)
{int lockval = 1;asm volatile ("xchg %[locked], %[lockval]": [locked] "=m" (sl->locked), [lockval] "=q" (lockval): "[lockval]" (lockval): "memory");return lockval == 0;
}

整体思路比较简单,此处不做解释。

2. ticketlock

/*** The rte_ticketlock_t type.*/
typedef union {uint32_t tickets;struct {uint16_t current;uint16_t next;} s;
} rte_ticketlock_t;

结构体中有三个变量:

  • tickets:ticket,分为高低十六位;
  • current:当前持有锁的cpu;
  • 下一个即将持有锁;

ticketlock主要解决的问题是,当各个CPU的性能存在较小的差异,或者在调度过程中不同的调度优先级和不同的时间片,都会影响到原始spinlock获取锁线程的争抢顺序有可能先争抢lock的线程抢不到锁,而后来的线程却先抢到了,ticketlock就解决了这个问题。下面直接看实现(做了一定简化):

static inline void
rte_ticketlock_lock(rte_ticketlock_t *tl)
{uint16_t me = __atomic_fetch_add(&tl->s.next, 1, __ATOMIC_RELAXED);rte_wait_until_equal_16(&tl->s.current, me, __ATOMIC_ACQUIRE);
}static inline void
rte_ticketlock_unlock(rte_ticketlock_t *tl)
{uint16_t i = __atomic_load_n(&tl->s.current, __ATOMIC_RELAXED);__atomic_store_n(&tl->s.current, i + 1, __ATOMIC_RELEASE);
}static inline int
rte_ticketlock_trylock(rte_ticketlock_t *tl)
{rte_ticketlock_t old, new;old.tickets = __atomic_load_n(&tl->tickets, __ATOMIC_RELAXED);new.tickets = old.tickets;new.s.next++;if (old.s.next == old.s.current) {if (__atomic_compare_exchange_n(&tl->tickets, &old.tickets,new.tickets, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))return 1;}return 0;
}

3. mcslock

mcslock因发明者而得名,John M. Mellor-CrummeyMichael L. Scott。在官方解释,mcslock主要解决的问题在于:

  • 提供可扩展/可伸缩的自旋锁;
  • 避免缓存震荡;
  • 提供公平锁;

首先看下它的结构:

/*** The rte_mcslock_t type.*/
typedef struct rte_mcslock {struct rte_mcslock *next;int locked; /* 1 if the queue locked, 0 otherwise */
} rte_mcslock_t;

首先还是关注三个方面,lock,unlock,和 trylock

static inline void
rte_mcslock_lock(rte_mcslock_t **msl, rte_mcslock_t *me)
{rte_mcslock_t *prev;/* Init me node */__atomic_store_n(&me->locked, 1, __ATOMIC_RELAXED);__atomic_store_n(&me->next, NULL, __ATOMIC_RELAXED);prev = __atomic_exchange_n(msl, me, __ATOMIC_ACQ_REL);if (likely(prev == NULL)) {return;}__atomic_store_n(&prev->next, me, __ATOMIC_RELAXED);__atomic_thread_fence(__ATOMIC_ACQ_REL);while (__atomic_load_n(&me->locked, __ATOMIC_ACQUIRE))rte_pause();
}static inline void
rte_mcslock_unlock(rte_mcslock_t **msl, rte_mcslock_t *me)
{if (likely(__atomic_load_n(&me->next, __ATOMIC_RELAXED) == NULL)) {rte_mcslock_t *save_me = __atomic_load_n(&me, __ATOMIC_RELAXED);if (likely(__atomic_compare_exchange_n(msl, &save_me, NULL, 0,__ATOMIC_RELEASE, __ATOMIC_RELAXED)))return;__atomic_thread_fence(__ATOMIC_ACQUIRE);while (__atomic_load_n(&me->next, __ATOMIC_RELAXED) == NULL)rte_pause();}__atomic_store_n(&me->next->locked, 0, __ATOMIC_RELEASE);
}static inline int
rte_mcslock_trylock(rte_mcslock_t **msl, rte_mcslock_t *me)
{__atomic_store_n(&me->next, NULL, __ATOMIC_RELAXED);rte_mcslock_t *expected = NULL;return __atomic_compare_exchange_n(msl, &expected, me, 0,__ATOMIC_ACQ_REL, __ATOMIC_RELAXED);
}

首先看下完整的用法:

#include <stdio.h>
#include <pthread.h>#include "rte_mcslock.h"rte_mcslock_t *pmcslock;void *task1(void*arg){rte_mcslock_t ml;rte_mcslock_lock(&pmcslock, &ml);printf("MCS lock taken on thread %ld\n", pthread_self());sleep(2);rte_mcslock_unlock(&pmcslock, &ml);
}void *task2(void*arg){rte_mcslock_t ml;sleep(1);rte_mcslock_lock(&pmcslock, &ml);printf("MCS lock taken on thread %ld\n", pthread_self());rte_mcslock_unlock(&pmcslock, &ml);
}int main()
{pthread_t t1, t2;pthread_create(&t1, NULL, task1, NULL);pthread_create(&t2, NULL, task2, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);
}

输出为:

$ ./a.out
MCS lock taken on thread 140330136704768
MCS lock taken on thread 140330128312064

整体思路就是:为了方式缓存cache震荡,为每个线程提供自己的变量锁,这样,就克服了使用像原始的spinlock那样使用volatile关键字不使用cache造成的不能发挥缓存的优势的问题,同时,解决了缓存震荡问题。

4. rwlock

读写自旋锁的数据结构:

typedef struct {volatile int32_t cnt; /**< -1 when W lock held, > 0 when R locks held. */
} rte_rwlock_t;

从数据结构来看,和常规的自旋锁基本一致,除了将lock改成了计数器,这个cnt是用来计算读者个数的。

static inline void
rte_rwlock_read_lock(rte_rwlock_t *rwl)
{int32_t x;int success = 0;while (success == 0) {x = __atomic_load_n(&rwl->cnt, __ATOMIC_RELAXED);/* write lock is held */if (x < 0) {rte_pause();continue;}success = __atomic_compare_exchange_n(&rwl->cnt, &x, x + 1, 1,__ATOMIC_ACQUIRE, __ATOMIC_RELAXED);}
}static inline int
rte_rwlock_read_trylock(rte_rwlock_t *rwl)
{int32_t x;int success = 0;while (success == 0) {x = __atomic_load_n(&rwl->cnt, __ATOMIC_RELAXED);/* write lock is held */if (x < 0)return -EBUSY;success = __atomic_compare_exchange_n(&rwl->cnt, &x, x + 1, 1,__ATOMIC_ACQUIRE, __ATOMIC_RELAXED);}return 0;
}static inline void
rte_rwlock_read_unlock(rte_rwlock_t *rwl)
{__atomic_fetch_sub(&rwl->cnt, 1, __ATOMIC_RELEASE);
}static inline int
rte_rwlock_write_trylock(rte_rwlock_t *rwl)
{int32_t x;x = __atomic_load_n(&rwl->cnt, __ATOMIC_RELAXED);if (x != 0 || __atomic_compare_exchange_n(&rwl->cnt, &x, -1, 1,__ATOMIC_ACQUIRE, __ATOMIC_RELAXED) == 0)return -EBUSY;return 0;
}static inline void
rte_rwlock_write_lock(rte_rwlock_t *rwl)
{int32_t x;int success = 0;while (success == 0) {x = __atomic_load_n(&rwl->cnt, __ATOMIC_RELAXED);/* a lock is held */if (x != 0) {rte_pause();continue;}success = __atomic_compare_exchange_n(&rwl->cnt, &x, -1, 1,__ATOMIC_ACQUIRE, __ATOMIC_RELAXED);}
}static inline void
rte_rwlock_write_unlock(rte_rwlock_t *rwl)
{__atomic_store_n(&rwl->cnt, 0, __ATOMIC_RELEASE);
}

DPDK 锁:ticketlock和mcslock相关推荐

  1. java 锁的类型_Java锁的种类 - shawnplaying的个人页面 - OSCHINA - 中文开源技术交流社区...

    Java锁和并发需要结合在一块了理解,涉及到了多个话题. 本文主要参考了 http://ifeve.com/java_lock_see1/ 但是我认为原文中有某些错误,我在下面的代码中做了修改. 公平 ...

  2. 一文足以了解什么是 Java 中的锁

    作者 |  cxuan 责编 | Elle Java 锁分类 Java 中的锁有很多,可以按照不同的功能.种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述 从线程是否需要对资 ...

  3. mysql 自旋锁,golang 自旋锁

    CAS算法(compare and swap) CAS算法是一种有名的无锁算法.无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步( ...

  4. Golang之自旋锁

    Golang之自旋锁 目录 Golang之自旋锁 自旋锁 golang实现自旋锁 可重入的自旋锁和不可重入的自旋锁 自旋锁的其他变种 1. TicketLock TicketLock主要解决的是公平性 ...

  5. 理解悲观锁乐观锁、同步锁、读锁、写锁

    ava 锁分类 Java 中的锁有很多,可以按照不同的功能.种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述 从线程是否需要对资源加锁可以分为 悲观锁 和 乐观锁 从资源已被 ...

  6. 悲观|乐观锁、自旋|互斥锁、公平|非公平锁

    解析锁--悲观|乐观锁.自旋|互斥锁.公平|非公平锁 悲观锁 总认为最坏的情况可能会出现,即认为数据很可能会被他人修改,因此在持有数据时总是先把资源或数据锁住.这样其他线程要请求这个资源时就会阻塞,直 ...

  7. Java锁——什么是锁?

    创作不易,原文: https://mp.weixin.qq.com/s?__biz=MzkwMDE1MzkwNQ==&mid=2247496038&idx=1&sn=10b96 ...

  8. 自旋锁原理及其应用场景

    题外话 自旋锁不是一种锁(类型),自旋锁是线程没有获取到锁时的一种等待策略. 自旋锁的提出背景 由于在多处理器环境中某些资源的有限性,有时需要互斥访问(mutual exclusion),这时候就需要 ...

  9. Java并发编程—常见面试题

    建议: 学习java并发前需要先掌握JVM知识 关于下面问题档案的详细解析都在后面推荐的相关系列文章中 一.线程安全相关 1.什么叫线程安全? 线程安全就是说多线程访问同一代码,不会产生不确定的结果. ...

最新文章

  1. jquery和JavaScript区别
  2. 欧几里得范数_机器学习中的范数究竟是个什么鬼?
  3. 2、(整数类型)INT、TINYINT、SMALLINT、MEDIUMINT、BIGINT
  4. 他们是最懂数据的商家!智能品牌时代到来
  5. 科学历史也可以写的如此精彩 ——《量子物理史话:上帝掷骰子吗》读后感
  6. 【网址收藏】主流Ansible Web UI的部署与使用:Tower AWX Semaphore TiOps
  7. java线程死锁_Java并发:隐藏线程死锁
  8. 【报告分享】2020年抖音kol生态研究报告.pdf(附下载链接)
  9. 产生斜体的html标签,下列可以产生斜体字的 HTML 标签是_____________
  10. 双系统重装Ubuntu经验分享
  11. 美国ipv6云服务器配置,Vultr服务器添加ipv6地址的方法
  12. Linux文本比较指令(diff,cmp)详解
  13. RPG Maker MV 打包安卓和修改分辨率的方法
  14. 免费在线证件照制作-超级好用
  15. Whole Word Masking (wwm) BERT PaddlePaddle常用预训练模型加载
  16. 不会编程,别着急!免编程工具助你快速开发App
  17. Grade for Android 之二:Groovy 与Java的语法区别
  18. 建筑师妹岛和世为日本西武设计新型旗舰“球形车头”列车
  19. ListView的简单使用《一》—普通图文展示案例
  20. three.js开发全景视频播放器的现实方法

热门文章

  1. Java使用iText实现对PDF文件的操作
  2. java 语法检查_java编译期间的语法检查
  3. 对数据类型而言运算符无效。运算符为 add,类型为 text。
  4. Pi 3B+编译安装python3.6.8
  5. 微信小程序Server端环境配置
  6. Android应用程序文件缓存getCacheDir()和getExternalCacheDir()
  7. MFC多文档中opencv处理图像打开、保存
  8. 4-3-串的块链存储结构-串-第4章-《数据结构》课本源码-严蔚敏吴伟民版
  9. QTP中实现对文本文件(txt)的读写操作
  10. 转载 Android入门学习_代码常用布局