本地自旋锁与信号量/多服务台自旋队列-spin wait风格的信号量
其实这是一个典型的多服务台排队问题,可是超市处理的并不好。存在队头拥塞问题。我就好几次遇到过。好几次,我排的那个队。前面结账出现了纠纷。我们后面的就必须等待,眼睁睁看着旁边的结账队伍向前推进。可是这样的排队方案足够简单。把调度任务交给了排队者本人,结账的人想排到哪个队列就排到哪个队列,推断一个队列是否会拥塞也有非常多办法,比方看购物的多少。是否有衣物(锁卡拔出纠纷),是否有称重的东西(会忘记称重)。是否有打折物。是否有老年人。收银员的手法是否娴熟等。全靠自己的推断。无异于一场赌博。 我改造的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内核的排队自旋锁的实现非常接近了。在此不谈优化。然而实际使用时,应该是先用汇编编码。然后汇编码优化它了。
本地自旋锁与信号量/多服务台自旋队列-spin wait风格的信号量的更多相关文章
- Linux 内核同步之自旋锁与信号量的异同【转】
转自:http://blog.csdn.net/liuxd3000/article/details/8567070 Linux 设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导 ...
- linux 自旋锁和信号量【转】
转自:http://blog.csdn.net/xu_guo/article/details/6072823 版权声明:本文为博主原创文章,未经博主允许不得转载. 自旋锁最多只能被一个可执行线程持有( ...
- 自旋锁&信号量
1. 自旋锁 Linux内核中最常见的锁是自旋锁.一个自旋锁就是一个互斥设备,它只能有两个值:"锁定"和"解锁".如果锁可用,则"锁定"位被 ...
- 漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?(转)
知乎链接:https://zhuanlan.zhihu.com/p/57354304 1. 锁的由来? 学习linux的时候,肯定会遇到各种和锁相关的知识,有时候自己学好了一点,感觉半桶水的自己已经可 ...
- linux自旋锁、互斥锁、信号量
为了避免并发,防止竞争.内核提供了一组同步方法来提供对共享数据的保护. 我们的重点不是介绍这些方法的详细用法,而是强调为什么使用这些方法和它们之间的差别. Linux 使用的同步机制可以说从2.0到2 ...
- 【APUE】信号量、互斥体和自旋锁
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html http://blog.chinaunix.net/uid-205 ...
- Linux内核同步:自旋锁
linux内核--自旋锁的理解 自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对于UP系统,自旋锁仅做抢占和中断操作,没有实现真正的“自旋”.如果配置了CON ...
- (linux)自旋锁及其衍生锁
自旋锁 毫秒以下. 自旋锁用于多个CPU系统中,在单处理器系统中,自旋锁不起锁的作用,只是禁止或启用内核抢占.在自旋锁忙等待期间,内核抢占机制还是有效的,等待自旋锁释放的线程可能被更高优先级的线程 ...
- Linux设备驱动程序 之 自旋锁
概念 自旋锁可以再不能休眠的代码中使用,比如中断处理例程:在正确使用的情况下,自旋锁通常可以提供比信号量更高的性能: 一个自旋锁是一个互斥设备,它只能由两个值,锁定和解锁:通常实现为某个整数值中的单个 ...
随机推荐
- P4396 [AHOI2013]作业 分块+莫队
这个题正解是莫队+树状数组,但是我个人非常不喜欢树状数组这种东西,所以决定用分块来水这个题.直接在莫队维护信息的时候,维护单点同时维护块内信息就行了. 莫队就是这几行核心代码: void add(in ...
- JavaScript:DOM对象
ylbtech-JavaScript:DOM对象 1. HTML DOM Document 对象返回顶部 1. HTML DOM Document 对象 HTML DOM 节点 在 HTML DOM ...
- Node.js:GET/POST请求
ylbtech-Node.js:GET/POST请求 1.返回顶部 1. Node.js GET/POST请求 在很多场景中,我们的服务器都需要跟用户的浏览器打交道,如表单提交. 表单提交到服务器一般 ...
- ZOJ-3988 2017CCPC-秦皇岛 Prime Set 二分图最大匹配 匈牙利
题面 题意:给你n个数,你可以选择2个和为质数的数为一对,每个数可以重复选择,你最多选k对,问你最多能选多少个不同数出来 题解:首先思考怎么样的数和为质数,2个偶数相加不行,除了1+1以外2个奇数相加 ...
- <a>和<table>标签的应用
今天介绍一下html中最重要的标签 标签分为 1.一般标签 如<img> <b></b>等 2.功能标签 如<a></a> 3.实体 如&a ...
- WCF与 Web Service的区别是什么?各自的优点在哪里呢?
这是很多.NET开发人员容易搞错的问题.面试的时候也经常遇到,初学者也很难分快速弄明白 Web service: .net技术中其实就指ASP.NET Web Service,用的时间比较长,微软其实 ...
- MeayunDB学习笔记(一) MeayunDB介绍及安装
系列目录 MeayunDB介绍-高性能分布式内存数据库 MeayunDB学习笔记(一)MeayunDB介绍及安装 MeayunDB学习笔记(二)批量导入数据 MeayunDB学习笔记(三)索引应用 ...
- C#中跨线程访问控件
net 原则上禁止跨线程访问控件,因为这样可能造成错误的发生,推荐的解决方法是采用代理,用代理方法来间接操作不是同一线程创建的控件. 第二种方法是禁止编译器对跨线程访问作检查,可以实现访问,但是出不出 ...
- 找回消失的ubuntu启动选项
启动菜单消失不是第一次了,这一次还是记录下来吧 原文链接:http://blog.chinaunix.net/uid-26527046-id-3748986.html 在安装windows后安装的ub ...
- Vs2010无法打开文件“Kernel32.lib”、无法打开“libcpmt.lib”"msvcprt.lib"
1.对于无法打开"Kernel"问题,即使复制lib文件到目录,仍然会出现最后的错误; 原因:WindowsSdk 安装失败! 方法:重装 microsoft SDK6.0 ,再在 ...