用人话讲内核2 - 同步

讲内核的过程中,也解释一下一些比较重要的概念。想想自己当年学的时候学的也是非常费劲。


同步(synchronization)是并发编程中很重要的概念。涉及到critical section,race condition,lock,mutex, semaphore,condition variable等等概念,不可谓不复杂。为了理解清楚这些,请各位看官听我慢慢道来。


1. 什么叫同步?为什么这是一个问题?


“同步”从字面上看什么都看不出来。。。但是同步问题却是普遍存在的。比如一个任务实现“两个人交替拍手”。实现两个人交替拍手实际就是一个同步问题。但是实现它并不如看起来那么简单,尤其是两个人相隔万里,很难通信的时候。


同步需要基于信息的传递或者共享。如果两个人能相互看见或者相互听见,那么交替拍手就容易。如果两个人相隔万里,就得基于某种通信手段或者共享某个媒介。比如共享电话线,大家能听到对方的拍手声就好办了。或者共享夜空,往天上放信号弹,交替拍手交替放信号弹。或者发短信,我拍一次,发条“已拍”短信,对方再来拍。这个看起来是不是有一点点信号量的意思?


这就是同步问题,其实是协同的意思。多个个体按照预先约定好的规则协同作业。


2. 为什么计算机程序需要同步?


交替拍手的确是一个同步问题,但貌似显得不是很重要。但是在现实世界里有些同步问题是至关重要的。


比如抢火车票,最后一张票,十个人同时下单,这票归谁?假如抢到票的人订完不付钱,票还得返回去。所以必须保证票的归属是互斥的,即任何时间只能归属于一人。而且在第一个人抢到单到付完钱期间,都要保证其他人拿不到票。这就是一个互斥问题。


另一个问题,假设庆丰包子每小时生产一笼包子,大家来消费。那么当包子滞销,库存满的时候就应该停止蒸包子;当客户买完的时候,就应该开始排队。这就是一个生产者消费者问题。


这些问题看起来都很直观,但为什么到了计算机里就那么烦呢。因为程序间共享的信息很有限,主要依靠内存。就像天下漆黑一片,伸手不见五指,大家谁也看不见谁,谁也听不到谁,唯一能做的是在夜幕上用灯光打字(共享内存),让天下人看到。在这种环境下,票怎么卖?包子怎么抢??


3. 策略,算法,原语


这是本文要说的重点,这是我们应该如何看待同步问题。先放下锁,信号量,条件变量等等等,让我们用 策略,算法,原语的方法来分析问题。


同步策略,就是一个同步问题的解决方案,是一个想法,是目的。


原语,是我们的工具,是手段。


算法则是利用工具实现方案的方法。


三者的关系是,为一个问题确定一种解决方案,选择原语,开发算法。


让我们从同步策略看起。最重要的同步策略就一个:互斥。一个数据任何时刻只能被一个个体访问。实现这个策略可用的原语实际是随意的,可以用锁,可以用mutex,可以用信号量等等。


锁也有很多种,spinlock, ticket spinlock,read write lock。这些锁从互斥的角度来讲都是一样的,区别在于ticket lock有FIFO得特性,所以保证公平性。读写锁则是在互斥的基础上,加入了对并发读的优化,这实际不是严格意义的互斥,因为同时有多个读者,但只要不更新,多个读者是不影响正确性的。所以在计算机的互斥中,主要关注写操作。当然,买票那个例子除外,一个人进入订票流程,别人看也不行。


如果选用mutex,其实mutex和锁用起来是差不多的。这时候需要考虑的就是互斥以外的因素,比如性能。锁是忙等的,也就是当锁被持有时,申请者会执行Pause指令,等于啥也不干,不断把CPU的计算能力白白浪费掉。所以如果等的比较久的话,推荐用mutex。Mutex就是一个会挂起的锁,申请者申请失败后,会挂起自己,把CPU让给别人,等到mutex被释放的时候再被唤醒。这样就用挂起和唤醒的代价换取CPU时间给别人用。计算机的世界到处是tradeoff (权衡),并不是挂起就一定好。因为如果挂起和唤醒的代价太大,而等的时间很短,还不如忙等呢。


如果用信号量做互斥也是可以的,比如binary semaphore。这就有点杀鸡用牛刀的意思了,所以一般大家不这么干。


其他常见同步策略还有生产者消费者问题。标准做法是选用mutex保证互斥,再加两个信号量,保证满和空得时候block。


4. 其他


4.1 race condition 和 critical section


这两个概念真是把简单的问题复杂化了,实际都是有互斥衍生出来的。为了实现互斥,我们加锁。加锁的那段代码我们叫他critical section。代码写的不好,该互斥的地方没互斥,把数据该乱了,就叫race condition。


关于critical section 需要注意的是,实际分析并发程序的时候是对“数据”加锁,不是对“代码”加锁。我们锁的是共享的数据,所以访问这段共享的数据的代码都要持有锁。这样思考比对“代码”加锁更有效,锁粒度也会更小,你就不会写出入函数拿锁,出函数释放锁这种粗犷的程序了。


4.2 关于spinlock 和 pthread_mutex


spinlock在kernel中比较常用,有两个原因。一、在持锁时间短的时候,忙等比挂起唤醒更加高效。二、在某些内核代码断中,比如linux interrupt processing,是没法挂起的。因为这段中断处理代码有些孤魂野鬼的意思,出了三界外,不在五行中,根本没法挂。


但是在userspace中,一般都用pthread_mutex。 前面说了,spinlock和mutex都可以实现互斥,所以功能上是一样的。区别在于性能。Userspace thread是会被OS挂起的,从而引起了著名的lock holder preemption问题。假设线程间用锁做互斥,当一个持锁线程被OS挂起时,其它线程为了拿锁就会忙等。正常情况下锁等待时间一般是10ns级(因为通常持锁只执行不多的指令),出现lock holder preempption时等待时间会成百上千倍增长。因为一个被挂起的线程通常过几毫秒才会回来释放锁。注意这种挂起不是自愿的,而是OS挂起一个线程把CPU分给了别人。这些等待时间的CPU cycle都被浪费掉了。所以我们用mutex,虽然等待时间不会变短,但是等待线程会被挂起,CPU资源能被让出来给需要的人。当然,如果你说所有的资源都归我,浪费了无所谓,那你就可以大胆在多线程程序里用spinlock了!


4.3 关于无锁内核


最近一个专门为Java和虚拟化优化的操作系统OSv直接搞出一个无锁内核。为什么?


前面提到kernel经常用spinlock,但是userspace不推荐,因为用户态线程会被内核挂起。现在同样的问题发生在了内核身上:虚拟机里的virtual CPU可能被虚拟机管理器(hypervisor)挂起。于是完全一样的lock holder preemption问题出现了。而且即使现在最新的linux kernel也没完全避免这个问题。


所以,内核也不要用spinlock了,用mutex吧。但是刚才还提到了一些出了三界外,不在五行中的孤魂野鬼。为了让它们也支持用mutex,OSv把它们也都收编进了kernel thread的模型,于是它们也可以被挂起了。


4.4 什么时候选用什么原语


线程间用什么,进程间用什么?简单的说,只要能share,大家都能看到相同的变量,用啥对线程进程没区别。选一个简单的,合适的就好。


4.5 Golang Channel


Google Golang 里边有一个新的同步原语叫做channel。这个原语用来做啥最有效我还不是很熟,但是起码生产者消费者问题就很容易解决,生产者自动往channel里丢数据,channel满了自动block了就。消费者也是直接取,channel空了就block。剩下的就交给编译器吧!


5. 结论


本文重点就像让大家分清同步策略和同步原语。在交替拍手问题中,实现交替同步策略,需要知道两人共享什么信息媒介(原语)。无论是电话,短信,信号弹(原语),我们都能找到合适的方法(算法)来实现我们的策略,你说是不?


6. 后记


《用人话讲内核》系列主要介绍操作系统的概念,作者根据自己多年的学习和理解,不谈实现细节,主要介绍概念和思想。 供初学者参考讨论。有误之处,多多指教。 以后可能会慢慢谈到虚拟化技术,资源管理,云计算等等。东西很多,慢慢聊。


关于作者


欧阳马上毕业,中国科学技术大学学士,美国匹兹堡大学在读博士生,研究方向操作系统,虚拟化。2013,2014年暑假作为实习生在美国VMware, Palo Alto ESX内核组从事CPU调度研发工作。

CCF大数据与计算智能大赛-面向电信行业存量用户的智能套餐个性化匹配模型联通赛-复赛第二名-【多分类,embedding】.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值