简介
race condition是多线程的应用程序中经常遇到的问题,本文章接下来会解释什么是race condition,如何检测到它们以及如何解决这类问题。
Race condition
从定义来说,race condition是代码中一些执行结果取决于其执行的相对时间或者多线程交错执行的判断条件。它的结果是不可预测的。如何一个程序中不存在race condition,那么它就是线程安全的(thread-safe)。
例如有个转账程序,假设有两个账户,里面的余额都是500,现在要分两次从A账户转账300到B账户,如下图:
这两次转账操作分别有一个线程执行。如果第二次转账是在第一次转账后发生的,那么第二次转账就会判断发现A账户余额不足,从而转账失败。
但是,如果这两次转账在两个线程内同时执行,那么就可能出现不可预测的结果。如下图,两个线程检查A账户月都是500,都进行了转账操作,结果A账户最后余额是-100,这样的结果明显是不正确的。
如上转账过程,由于线程的执行时机是任意的,所以执行的结果也是不可预测的。造成这一情况的正是race condition(上面判断账户是否大于300的判断)。为了避免race condition,所以的共享资源(在例子中两个账户的余额可以被多个线程读取操作,所以余额就是共享资源)必须原子性地操作。打到这个目标有两个方法,第一种是将多个操作封装成一个线程排他性的代码段(例如加锁),第二是从硬件上保证是这些操作的原子性。
Race condition的两个模式
Check-Then-Act
Race condition有两个模式,第一个就是上面的转账的例子,属于Check-Then-Act
模式,这类模式通常是一个流程中有一个检查环节,通过检查的结果再决定后续怎么走。
懒加载是Check-Then-Act
的第二例子,例如在单例模式中,懒汉模式如下:
class Singleton {
public static Object singleton;
public static Object getSingleton(){
if (singleton == null) {
singleton = new Object();
}
return singleton;
}
}
上面的singleton == null
就是一个race condition,根据上面的解决办法,最简单是加上synchronized关键字,让判断和赋值两个操作具有线程排他性。
Read-Modify-Write
Read-Modify-Write
是race condtion的第二个模式。在大多数编程语言中,对一个变量的修改通常分为读取,修改,写入三个步骤。问题通常是对变量的并发的读取修改(至少有一个修改,纯读取是没问题的)引起的。例如:
由于count++
不是原子操作,所以可能发生以下情形:
两个线程同时对count进行++操作,结果并没有加2。
如何检测race condition
race condition通常很难重现,定位和排除,因为问题发生依赖于特定的场景。即使写一个多线程的单元也不可以保证程序的100%的正确定,但有一些方法可以排除race condition。
如何避免race condition
1.避免使用共享变量
在多线程环境中,race condtion的出现往往伴随着共享变量,如上面例子中的账户余额和count变量,都可以被多个线程读取操作,那么避免问题发生最简单直接的办法就是避免使用共享变量。可以使用不可变对象或者线程本地对象(例如java中的threadlocal)。
2.使用同步或者原子操作
使用同步可以避免race condition的问题,但是线程同步往往伴随很多同步的性能开销,还可能导致死锁。两个办法是使用原子操作,例如java中的提供的原子类。
今天的文章什么是Race Condition?分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/24623.html