时间:2021-05-22
当你有多个进程或线程访问相同的数据时,竞争条件是一个威胁。本文探讨了在发现竞争条件后如何测试它们。
Incrmnt
你在一个名为“Incrmnt”的火热新创公司工作,该公司只做一件事情,并且做得比较好。
你展示一个全局计数器和一个加号,用户可以点击加号,此时计数器加一。这太简单了,而且容易使人上瘾。毫无疑问这就是接下来的大事情。
投资者们争先恐后的进入了董事会,但你有一个大问题。
竞争条件
在你的内测中,Abraham和Belinda是如此的兴奋,以至于每个人都点了100次加号按钮。你的服务器日志显示了200次请求,但计数器却显示为173。很明显,有一些请求没有被加上。
先将“Incrmnt变成了一坨屎”的新闻抛到脑后,你检查下代码(本文用到的所有代码都能在Github上找到)。
你的Web服务器使用多进程处理流量请求,所以这个函数能在不同的线程中同时执行。如果你没掌握好时机,将会发生:
所以尽管增加了两次计数,但最终只增加了1。
你知道你可以修改这个代码,变为线程安全的,但是在你那么做之前,你还想写一个测试证明竞争的存在。
重现竞争
在理想情况下,测试应该尽可能的重现上面的场景。竞争的关键因素是:
?两个 get_count 调用必须在两个 set_count 调用之前执行,从而使得两个线程中的计数具有相同的值。
set_count 调用,什么时候执行都没关系,只要它们都在 get_count 调用之后即可。
简单起见,我们试着重现这个嵌套的情形。这里整 个Thread 2 在 Thread 1 的首个 get_count 调用之后执行:
before_after 是一个库,它提供了帮助重现这种情形的工具。它可以在一个函数之前或之后插入任意代码。
before_after 依赖于 mock 库,它用来补充一些功能。如果你不熟悉 mock,我建议阅读一些优秀的文档。文档中特别重要的部分是 Where To Patch。
我们希望,Thread 1 调用 get_count 后,执行全部的 Thread 2 ,之后恢复执行 Thread 1。
我们的测试代码如下:
在首次 get_count 调用之后,我们使用 before_after 的上下文管理器 after 来插入另外一个 increment 的调用。
在默认情况下,before_after只调用一次 after 函数。在这个特殊的情况下这是很有用的,因为否则的话堆栈会溢出(increment调用get_count,get_coun t也调用 increment,increment 又调用get_count…)。
这个测试失败了,因为计数等于1,而不是2。现在我们有一个重现了竞争条件的失败测试,一起来修复。
防止竞争
我们将要使用一个简单的锁机制来减缓竞争。这显然不是理想的解决方案,更好的解决方法是使用原子更新进行数据存储——但这种方法能更好地示范 before_after 在测试多线程应用程序上的作用。
在 incrmnt.py 中添加一个新函数:
# incrmnt.py def locking_increment(): with db.get_lock(): return increment()它保证在同一时间只有一个线程对计数进行读写操作。如果一个线程试图获取锁,而锁被另外一个线程保持,将会引发 CouldNotLock 异常。
现在我们增加这样一个测试:
现在在同一时间,就只有一个线程能够增加计数了。
减缓竞争
我们这里还有一个问题,通过上边这种方式,如果两个请求冲突,一个不会被登记。为了缓解这个问题,我们可以让 increment 重新链接服务器(有一个简洁的方式,就是用类似 funcy retry 的东西):
当我们需要比这种方法提供的更大规模的操作时,可以将 increment 作为一个原子更新或事务转移到我们的数据库中,让其在远离我们的应用程序的地方承担责任。
总结
Incrmnt 现在不存在竞争了,人们可以愉快地点击一整天,而不用担心自己不被计算在内。
这是一个简单的例子,但是 before_after 可以用于更复杂的竞争条件,以确保你的函数能正确地处理所有情形。能够在单线程环境中测试和重现竞争条件是一个关键,它能让你更确定你正在正确地处理竞争条件。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
问题背景在同一套系统中,要支持连接访问各种流行的数据库,以及同一数据库的不同版本,例如,oracle9i、oracle10g、oracle11g、oracle1
查询缓存的使用,主要是为了提高查询访问速度。将用户对同一数据的重复查询过程简化,不再每次均从数据库查询获取结果数据,从而提高访问速度。MyBatis的查询缓存机
在工作和学习中如果同时传输多个文件,大的安装包,python提供了一种无线传输的方法,开启一个本地http服务器,同一局域网下可方便访问经测试下载速度可达13M
什么是线程同步?当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。实现
Java并发编程之Semaphore(信号量)详解及实例概述通常情况下,可能有多个线程同时访问数目很少的资源,如客户端建立了若干个线程同时访问同一数据库,这势必