周日傍晚,我去家附近的超市(...)买苏打水,准备自制青柠苏打。我感觉我做的比买的那个巴黎水要更爽口。由于天气太热,非常多人都去超市避暑去了,超市也不撵人,这仿佛是他们的策略。人过来避暑了,走的时候难免要买些东西的。就跟非常多美女在公交地铁上看淘宝消磨时光,然后就下单了...这是多么easy一件事,反之开车的美女网购就少非常多。对于超市的避暑者,要比公交车上下单更麻烦些,由于有一个成本问题,这就是排队成本。
       其实这是一个典型的多服务台排队问题,可是超市处理的并不好。存在队头拥塞问题。我就好几次遇到过。好几次,我排的那个队。前面结账出现了纠纷。我们后面的就必须等待,眼睁睁看着旁边的结账队伍向前推进。可是这样的排队方案足够简单。把调度任务交给了排队者本人,结账的人想排到哪个队列就排到哪个队列,推断一个队列是否会拥塞也有非常多办法,比方看购物的多少。是否有衣物(锁卡拔出纠纷),是否有称重的东西(会忘记称重)。是否有打折物。是否有老年人。收银员的手法是否娴熟等。全靠自己的推断。无异于一场赌博。  我改造的OpenVPN多线程实现就是这样的。
       银行服务以及饭店的排队服务就要好非常多,顾客排队时。自取一个号码,排入单一的队列,由空暇服务台叫号。这就是一个调度系统。这样的单队列多服务台是不会出现队头拥塞的,等候的顾客持ticket排队。本身不必排在队伍里,而ticket号逻辑上组成一个虚拟的队列,没叫到号的能够临时干点别的,自身不必排队。

临时干别的?并不意味着你能够离开。特别是业务处理流程非常快的情况下。

你离开大厅。刚走出去。准备去旁边的小店逛逛,结果听到叫到你的号了,赶紧返回。其实还不如不出去呢。可是对于等待比較久的叫号系统,那倒是能够临时出去。出去再返回的过程意味着体力开销,可是如果出去的时间久,能够完毕另一件重要的事,意味着为这另外这件事的收益付出的体力开销是值得的。

知道我想到什么了吗?我想到了信号量。

信号量就是一个单队列多服务台排队系统,信号量的初始值就是服务台的数量。

一个运行流被服务意味着少了一个可服务的服务台。这就是down操作,而up操作则是一个服务台又一次变成空暇的信号,这意味着有一个新的排队者能够得到服务了,我能够把”服务“理解成进入临界区。

我在想一个问题,为什么信号量一定要设计成sleep-wait的模式,为什么就没有spin-wait的模式啊。而我眼下面临的问题,如果使用sleep-wait,切换开销太大,perf显示的头几名大头都在schedule,wake up。之类的,也就是说,你切换出去了,没多久就又把你叫回来了,好在Linux调度系统基于CFS全然公平机制,抖动不会太厉害。只是这么切换一次造成的开销也不算小。起码等到再次切换回来的时候。cache变凉了。

回想Linux版的ticket自旋锁。我认为全部的排队者以及持锁者touch同一个变量,该变量会cache到全部的当事者cpu的cache中,被持锁者以及争锁者read/write时,会涉及到多个处理器之间的cache一致性问题,这也是一笔非常大的底层开销。于是我设计了一个本地接力自旋锁改变了这个局面,保持每个争锁者都仅仅touch一个别的争锁者不会touch的变量。且cache line要着色以保证不会cache到同一line,此外,持锁者在释放锁的时候,仅仅会write下一个争锁者的本地变量。

这样就确保了cache一致性被最少的触发。
       本着这个新的自旋锁设计,结合我在超市的经历,我想把我这个自旋锁发展成一个能够有多个CPU持有锁的自旋队列。后来我突然发现,这不就是信号量嘛...可惜信号量并没有如期被我所用,由于Linux实现的信号量是sleep-wait机制的,我须要的是spin-wait,由于我知道一个数据包的发送是非常快的,之所以引入队列。构建VOQ,是由于我想避开N加速比问题,然而我的算法是软实现,根本不存在N加速比问题,所以后来我想取消VOQ,又怕引发队头拥塞。所以採用了多服务台单队列机制,为了实现这个,我本能够採用信号量的,可是又不想sleep,所以採用极其复杂的多个spin lock的机制,超市排队引发的遐想导致我想到用spin-wait来实现信号量,其实,简单測试之后。发现效果还真不错。

先看一下Linux原生的信号量实现。代码比較简单。顺便说一句。这篇文章并不意味着我又開始源码分析了,而是或许它意味着某种终结,前后的呼应。

/** 为了突出重点问题,不至于迷失在代码细节.我做了下面的如果:* 1.我省去了操作信号量本身的自旋锁,我如果P/V操作过程的随意序列都是原子的.* 2.我取消了超时參数以及state,我如果除非得到信号量,否则一定等下去,我还如果睡眠不会被打断,除非有人唤醒.* 3.我取消了inline,由于我想突出环绕本地栈变量本地自旋,这样不会cache pingpong.*/
struct semaphore {raw_spinlock_t        lock;unsigned int        count;struct list_head    wait_list;
};struct semaphore_waiter {struct list_head list;struct task_struct *task;// 本地局部检測变量bool up;
};static int down(struct semaphore *sem)
{if (likely(sem->count > 0)) {sem->count--;}else {struct task_struct *task = current;struct semaphore_waiter waiter;// 栈上的排队体,相当于ticket,获得信号量(函数返回)后就没实用了list_add_tail(&waiter.list, &sem->wait_list);waiter.task = task;waiter.up = false;for (;;) {__set_task_state(task, TASK_UNINTERRUPTIBLE);schedule();// 本地栈变量的检測,降低了多处理器之间的cache同步。不会cache乒乓// ********************************************************************// 可是要想到一种情况。如果多个进程试图写这个变量,还是要有锁操作的。

// 尽管我的如果是全部操作以及操作序列都是原子的,可是在up操作中。持有信 // 号量的进程仅仅是简单的wake up了队列,而这并不能确保被唤醒的task就一定可 // 以得到运行,中间另一个schedule层呢。鉴于这样的复杂的局面,我想到了不 // sleep,而是本地自旋版本号的信号量。无论如何,它确实攻克了我的问题。

// [其实,由于sem本身拥有一把自旋锁,这就禁止了多个“服务台”同一时候召唤 // 同一个等待者的局面,而我在我的描写叙述中,忽略了这把自旋锁,这是为什么呢? // 由于。我想为我的自旋信号量版本号贴金,不然人家都把问题攻克了,我还扯啥 // 玩意儿啊!] // ******************************************************************** // 这样的情况在spin lock下不会存在,由于同一时候仅仅有一个进程会持有lock, // 不可能多个进程同一时候操作。

if (waiter.up) { return 0; } } } } void up(struct semaphore *sem) { unsigned long flags; if (likely(list_empty(&sem->wait_list))) { sem->count++; } else { struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list, struct semaphore_waiter, list); // 标准的Linux kernel中。该操作被spin lock保护,这意味着不可能多个服务台同一时候将 // 服务给与同一个等待者。 list_del(&waiter->list); waiter->up = true; // 简单wake up进程。它何时投入运行,看调度器何时调度它了。 wake_up_process(waiter->task); } }

由于我忽略了信号量本身的保护自旋锁,当你具体分析上述实现的时候。会发现非常多竞争条件,比方同一时候多个服务台召唤一个等待者,可是没关系,该说的我都写到冗长的凝视里面了。

我之所以忽略信号量的自旋锁,是由于我想把信号量该造成一个通用的自旋等待队列,自旋锁仅仅是当中一个特殊情况,该情况相应仅仅有一个服务台的情形。
       如果看懂了原生的实现,那么改造后的实现应该是下面的样子:

​/** 我引入了BEGIN_ATOMIC和END_ATOMIC两个宏。由于我不想贴汇编码。所以这两个宏的意思就是它们之间的代码都是由* lock前缀修饰的,锁总线。

* 此外,什么事情都没有做。仅仅是改了名称。如果想初始化一个标准的排队自旋锁,将初始化宏的val设置成1就可以。 */ struct spin_semaphore { unsigned int count; struct list_head wait_list; }; struct spin_semaphore_waiter { struct list_head list; struct task_struct *task; // 本地局部检測变量 bool up; }; static int spin_down(struct spin_semaphore *sem) { if (likely(sem->count > 0)) { sem->count--; } else { struct task_struct *task = current; struct spin_semaphore_waiter waiter; BEGIN_ATOMIC list_add_tail(&waiter.list, &sem->wait_list); waiter.task = task; waiter.up = false; END_ATOMIC for (;;) { cpu_relax(); // PAUSE if (waiter.up) { return 0; } } } } void up(struct spin_semaphore *sem) { unsigned long flags; BEGIN_ATOMIC if (likely(list_empty(&sem->wait_list))) { sem->count++; END_ATOMIC } else { struct spin_semaphore_waiter *waiter = list_first_entry(&sem->wait_list, struct spin_semaphore_waiter, list); list_del(&waiter->list); waiter->up = true; END_ATOMIC } }

全部名称加上了spin_前缀修饰。不错。这个应该是和Windows NT内核的排队自旋锁的实现非常接近了。在此不谈优化。然而实际使用时,应该是先用汇编编码。然后汇编码优化它了。

转载于:https://www.cnblogs.com/lytwajue/p/7071544.html

本地自旋锁与信号量/多服务台自旋队列-spin wait风格的信号量相关推荐

  1. 随机数在java 自旋锁的运用_Java 中自旋锁的实现

    Java中初始是使用mutex互斥锁,因为互斥锁是会线程等待挂起,而对获取锁后的操作时间比较短暂的应用场景来说,这样的锁会让竞争锁的线程不停的park,unpark 的操作,这样的系统的调用性能是非常 ...

  2. linux内核自旋锁解释,LINUX内核笔记:自旋锁

    目录 1.自旋锁作用与基本使用方法? 与其他锁一样,自旋锁也用于保护临界区,但是自旋锁主要是用于在SMP上保护临界区.在SMP上,自旋锁最多只能被一个可执行线程持有,如果一个线程尝试获得一个被争用的自 ...

  3. linux 进程调度 运行队列 自旋锁,linux内核进程调度(自旋锁)

    2.1首先让我们了解,操作系统分为两类:一类是实时操作系统,一类是分时操作系统.它们的共同特点是都是多任务的 .多任务操作系统分为两类:非抢占式多任务和抢占式多任务. 非抢占式多任务,就是指进程不断的 ...

  4. 自旋锁以及Java中的自旋锁的实现

    什么是自旋锁 多线程中,对共享资源进行访问,为了防止并发引起的相关问题,通常都是引入锁的机制来处理并发问题. 获取到资源的线程A对这个资源加锁,其他线程比如B要访问这个资源首先要获得锁,而此时A持有这 ...

  5. 信号量、互斥体和自旋锁

    一.信号量 信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信.本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况.一般说来,为了获 ...

  6. 自旋锁、互斥锁和信号量

    自旋锁 Linux内核中最常见的锁是自旋锁(spin lock).自旋锁最多只能被一个可执行线程持有.如果一个执行线程试图获得一个已经被持有的自旋锁,那么该线程就会一直进行忙循环--旋转--等待锁重新 ...

  7. Linux并发与竞争介绍(原子操作、自旋锁、信号量、互斥体)

    目录 并发与竞争 并发与竞争简介 保护内容是什么 原子操作 原子操作简介 原子整形操作API函数(atomic_t 结构体) 原子位操作API 函数 自旋锁 自旋锁简介 自旋锁API函数 线程与线程 ...

  8. Linux内核中的同步原语:自旋锁,信号量,互斥锁,读写信号量,顺序锁

    Linux内核中的同步原语 自旋锁,信号量,互斥锁,读写信号量,顺序锁 rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 1. Linux 内核中的同步原 ...

  9. Linux内核的并发与竞态、信号量、互斥锁、自旋锁

    /************************************************************************************ *本文为个人学习记录,如有错 ...

最新文章

  1. python如何读取mat文件可视化_python Matplotlib数据可视化(1):简单入门
  2. C言语教程第五章:函数(1)
  3. python基础知识面试题-[面试] 1. 关于Python的基础知识
  4. python中typeerror怎么解决_Python 3中套接字编程中遇到TypeError: 'str' does not support the buffer interface的解决办法...
  5. linux之 !!命令
  6. 产品经理的职责(转)
  7. PP视频怎么关闭PP视频虚拟键盘显示
  8. android 编译时解析xml布局,android – 在xml布局中引用build.gradle versionName属性
  9. 小知识—PRINCE2的七大原则之剪裁
  10. ArturiaFX Collection for Mac(音乐效果器合集) 2021最新
  11. js如何连接mysql数据库_js怎样连接和调用mysql数据库?
  12. springboot+vue+elementUI 公司财务固定资产管理系统#毕业设计
  13. IMAC双系统WIN下截屏快捷键
  14. 困扰所有SAP顾问多年的问题终于解决了
  15. FreeSwitch连接讯时语音网关配置和全部使用场景
  16. H G W S哪一个不是状态函数_师大版成语词典发布!不学不是师大人!
  17. PATC语言1-50
  18. vue系列教程之微商城项目|主页
  19. ubuntu下复制文件到另一个文件夹下权限不够问题解决方法
  20. Luogu2455 [SDOI2006]线性方程组 (高斯消元)

热门文章

  1. mysql建表测试_总结MySQL建表、查询优化实用小技巧
  2. 云计算成了“晕技术”就对了
  3. systemctl不可用时,使用service命令关闭防火墙
  4. 使用yum命令时Error: Cannot find a valid baseurl for repo: base
  5. js获取CuteEditor的值
  6. [转载] 嵌入式文件系统
  7. Ubuntu 安装 TPM-2.0 TSS 软件栈
  8. 10大Go语言开源项目推荐
  9. android adbd分析,android6.0 adbd深入分析(三)adb root重启adbd流程
  10. JDK8高性能队列“Disruptor“