作者 | Beekums
译者 | Rayden
审校 | 王强
并发错误臭名昭著,常常导致令人十分崩溃的 bug。大多数软件的 bug 是一致的。如果你先做 X,然后做 Y,然后做 Z,你将会得到 Bug A。
但通过并发,你会遇到竞争条件(race condition)。这是一个 bug,如果你做 X,然后做 Y,你可能有 10% 的几率得到 Bug A。错误的出现是间歇性的,这使得你很难找到错误根本原因,因为你不能可靠地重现它。这也使得你很难证明你确实解决了这个问题。如果 Bug A 发生的几率只有 10%,那么你就需要多次尝试重现该 Bug,以确信自己已经修复了它。
处理并发性问题是我职业生涯早期的谋生之道。我喜欢使用多线程并修复高级开发人员错过的竞争条件,这种工作可以大幅提升我自己的信心。
然后我参加了一个面试,并被问到并发问题。结果相当好。
就在那时,我意识到我擅长某种类型的并发问题,而这类问题恰好是大多数并发问题的原因。
1 简单并发问题实践
首先,让我们稍微讨论一下什么是并发。然后,我们将继续讨论一个简单的并发问题,然后是一个更复杂的问题。
并发基本上是让多个独立的代码段同时运行。让我们从假设开始,然后进入一个真实情况。
假设我需要对一个 API 发出 5 个不同的请求。每一个请求都需要 100 毫秒才能完成。如果我等待一个完成后才开始下一个,我将会等待 500ms。
如果我同时执行这 5 个 web 请求,我最终将等待 100 毫秒加上很少的一些额外开销。
这是一个相当大的性能差异,也是人们通常使用并发的原因所在。
这听起来像是一个简单的概念,对吧?这是因为它就是一个简单的概念。
问题在于执行过程。那些 API 请求每个耗时大约 100 毫秒,而不是精确的 100 毫秒。
这意味着你将按顺序发出 API 请求,但返回将是乱序的:
每次运行执行 API 请求的代码时,返回顺序都会不同。
你通过并发性获得了性能改进,但是放弃了一致性。
如果处理这些 API 请求响应的代码使用共享数据,就会出现 bug。
让我们看一个更详细的例子,看看这是如何发生的。Dynomantle 的搜索栏建议有个 bug。