译者按
Preshing 的博客是进修并发编程的不行多得的资料,讲授较量具体。身边的许多伴侣从中受益良多。
我们在和作者相同后,得到了授权,着手翻译了他的博客,劳务派遣管理系统,登载在这里,以飨伴侣诸君。
和一般的翻译差异,我们加上了独家的注释。注释要么是更正错误,要么是帮助领略,要么是增补扩展;相信对各人会大有裨益。
正文翻译:@Diting0x
审校 && 注释:@睡眼惺忪的小叶先森
原文地点: Preshing
原文
锁(也叫互斥量)在很长一段时间都被误解了。1986年,在Usenet的有关于多线程的接头会中,Matthew Dillon说过:大大都人都对锁有个误解,认为锁是慢的。25年后,这种误解好像在某一时间段又溘然呈现了。
在某些平台上可能当锁被高度竞争时,锁确实慢。别的,当你在开拓一个多线程措施时,由于锁的引入,给机能带来庞大的瓶颈是很常见的。但这并不料味着所有的锁都是迟钝的。我会在这篇文章中表明,有的时候,利用锁的计策反而能带来很是好的机能。
各人对锁的误解大概源自于某个最容易忽视的原因:不是所有的措施员城市意识到轻量级锁和内核锁的区别。我会在下一篇文章中对轻量级锁做专门先容:老是利用轻量级锁。在这篇文章中,假设你在Windows平台下做C/C++
开拓,你需要的正是一个Critical Section工具。
有时候,锁是慢的这个结论是由benchmark支撑的。譬喻,这篇文章在高负载状态下来测试锁的机能:每个线程必需持有锁来完成任何一项任务(高竞争),而且锁都是在极短的时距离断下被持有(高频率)。这种方法好像很完美,但在实际应用中,却要制止这种利用锁的方法注1。基于这种思量,我设计了一种benchmark,同时包括对锁利用的最坏环境和最好环境。
由于一些其它的思量,各人大概不肯意用锁了。存在一系列的技能被称为无锁编程(可能不含锁编程注2)。无锁编程是极具挑战性的,但其自己可以在很多实际应用场景下带来高度的机能回报。据我所知,有些措施员会耗费很多天甚至几周的时间来设计某种无锁算法,昆山软件开发,之后再做一系列测试,但在几个月后才发明埋没的bug. 风险与回报并存对付相当一部门措施员都是有诱惑力的,这虽然也包罗我,在今后的几篇文章中会提到这些。有了无锁编程的诱惑,各人开始以为锁利用起来很枯燥,迟钝而且很是糟糕。
但也不能把锁贬的一文不值。在现实软件中,当各工钱了掩护内存分派器的时候,锁就是一个让人敬仰的对象。Doug Lea的分派器是在游戏开拓中很是著名的内存分派器注3,但其只支持单线程,这时候我们就必需利用锁机制来举办掩护。在游戏运行时,常常会遇到多个线程抢占一个内存分派器,每秒钟抢占次数可到达15000次阁下。在加载进程中,每秒钟会到达100000次甚至更多。固然这并不是个大问题。但你却可以看到,锁能很是精彩的来处理惩罚这些负载。
锁竞争benchmark
在这次测试中,我们建设一个线程来生成随机数,回收传统的Mersenne Twister生成器来实现。此线程时而获取锁,时而释放锁。获取与释放锁的隔断时间是随机的,但它都很靠近我们提前决定出的平均值。举个例子,假设我们要每秒钟获取锁15000次,让持有锁的时间保持在总时间的50%. 下图是部门的timeline。赤色说明锁正在被持有,灰色说明锁被释放。
昆山软件开拓 的去研究这个问题" class="aligncenter size-full wp-image-28832" title="mutex1" src="/uploads/allimg/c180522/152E32T2BE0-1R15.jpg" />
这是个泊松漫衍进程。假如我们知道生成单个随机数的平均时间–在2.66GHz的四核Xeon处理惩罚器上需要6.349ns–那么我们用事情单位(work units)而不是秒来权衡时间。可以用我之前的文章中先容的要领,How to Generate Random Timings for a Poisson Process,算出获取与释放锁的时距离断有几多个事情单位。下面代码是C++
的实现。我省略了一些细节,喜欢的话,可以在 这里 下载完整的源码
QueryPerformanceCounter(&start); for (;;) { // Do some work without holding the lock workunits = (int) (random.poissonInterval(averageUnlockedCount) + 0.5f); for (int i = 1; i < workunits; i++) random.integer(); // Do one work unit workDone += workunits; QueryPerformanceCounter(&end); elapsedTime = (end.QuadPart - start.QuadPart) * ooFreq; if (elapsedTime >= timeLimit) break; // Do some work while holding the lock EnterCriticalSection(&criticalSection); workunits = (int) (random.poissonInterval(averageLockedCount) + 0.5f); for (int i = 1; i < workunits; i++) random.integer(); // Do one work unit workDone += workunits; LeaveCriticalSection(&criticalSection); QueryPerformanceCounter(&end); elapsedTime = (end.QuadPart - start.QuadPart) * ooFreq; if (elapsedTime >= timeLimit) break; }