-
09-06-2019 - |
题
当编写的多线程应用程序,最常见的一个问题是经验丰富的竞争条件。
我的问题对社区有:
什么是比赛的条件?你怎么检测到他们吗?你怎么处理他们?最后,你如何防止其发生?
解决方案
竞争条件发生在两个或更多线程可以存取共享数据和他们试图去改变它在同一时间。因为线调度的算法之间可以交换线在任何时候,你不知道的顺序线将试图访问该共享数据。因此,结果的变化的数据是依赖线调度的算法,即这两个线"赛"访问的/更改的数据。
问题往往发生在一个线程不会一个"检查,然后采取行动"(例如"检查"如果值X,然后"行为"来做一些事情,这取决于价值正在X)和另外一个线程的东西的价值之间的"检查"和"行为"。例如:
if (x == 5) // The "Check"
{
y = x * 2; // The "Act"
// If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
// y will not be equal to 10.
}
这一点是,y可能是10,或者它可以是任何东西,取决于是否有另外一个线程改变x之间的检查和采取行动。你有没有真正的方式的了解。
为了防止种族的条件下发生,通常会把锁周围的共用数据,以确保只有一线可以访问的数据的时间。这将意味着事情是这样的:
// Obtain lock for x
if (x == 5)
{
y = x * 2; // Now, nothing can change x until the lock is released.
// Therefore y = 10
}
// release lock for x
其他提示
一个"竞争条件"的存在,当多线程(或者平行)代码,将访问一个共享的资源可以这样做,在这样一种方式为会导致意外的结果。
采取这样的例子:
for ( int i = 0; i < 10000000; i++ )
{
x = x + 1;
}
如果你有5个线程执行这些代码,x的值不会最终被50,000,000.事实上,随着每个运行。
这是因为,为了每个线程,以增x的值,他们必须做到以下几点:(简化,显然)
Retrieve the value of x Add 1 to this value Store this value to x
任何线可以在任何步骤,在这一过程在任何时候,他们可以在时彼此共享资源的参与。国家的x改变可以通过另外一个线程的时间之间的x正在读时,它是写回。
让我们说有一线检索的x值,但没有保存它。另外一个线程还可以检索的 同 x值(因为没有线已经改变它还),然后他们就都被储存 同 值(x+1)回x!
例如:
Thread 1: reads x, value is 7 Thread 1: add 1 to x, value is now 8 Thread 2: reads x, 值是7 Thread 1: stores 8 in x Thread 2: adds 1 to x, value is now 8 Thread 2: 存储8在x
竞争条件能够避免通过采用某种 锁定 机构的代码之前,访问公共资源:
for ( int i = 0; i < 10000000; i++ )
{
//lock x
x = x + 1;
//unlock x
}
在这里,答复来作为50,000,000每次。
欲了解更多关于锁,搜索:锁,信,关键的部分,共享资源。
什么是比赛的条件?
你是打算去看电影在午5点。你询问有关提供的门票在4点。该代表说,他们都是可用的。你放松达到的售票窗口5分钟之前所示。我敢肯定你可以猜猜发生了什么:这是一个完整的家。这里的问题是在持续时间之间的检查和行动。你询问,在4并采取行动,在5.与此同时,其他人抓住了机票。这是一场竞赛的条件-特别是一个"检查-那么-行动"方案的竞争条件。
你怎么检测到他们吗?
宗教码的审查,多线程单元的测试。有没有捷径。有几个日食的插件新出现的在此,但是没有稳定。
你怎么处理和防止他们吗?
最好的事情就是创建的副作用免费和无国籍的功能,使用immutables尽可能多的。但是,这并不总是可能的。因此,使用java。工具.并行。原子,并发数据结构、适当同步,并演员于并发会帮助。
最好的资源并发JCIP.你也可以获得更多一些 细节上述解释这里.
还有一个重要的技术差异之间的竞争条件和数据的比赛。大多数的答案似乎假设,这些条款是等价的,但它们不是。
数据比赛时发生的2说明了访问相同的存储位置,至少有一个这些访问是一个编写和没有 发生前订购的 在这些访问。现在什么构成发生前订购受到了很多辩论,但一般ulock锁对在相同的锁定变量和等待信号对于相同的条件变促使发生之前的顺序。
比赛的条件是一个语义的错误。它是一个缺陷的发生在时间或订购的事件,导致错误的程序 行为.
许多竞争条件可以(而且在事实上都)而引起的数据的比赛,但这不是必要的。事实上,数据种族和种族的条件既不是必要的,也不是充分条件。 此 博文还解释的差异很好用一个简单的银行交易的例子。这里是另一个简单的 例 解释的差别。
现在,我们确定下来的术语中,让我们尝试回答原来的问题。
鉴于比赛的条件是语义的错误,没有一般的方式检测它们。这是因为没有办法具有自动化的oracle,可以区分正确的对不正确的程序的行为在一般的情况。比赛检测是一个无法判定的问题。
另一方面,数据的比赛有一个准确的定义,该定义不一定涉及正确性,因此一个可以检测到它们。有许多种的数据探测器竞赛(静态/数据动态的比赛检测,锁具为基础的数据的比赛检测,发生前所基于的数据的比赛检测,混合的数据的比赛检测)。一个国家的技术动态数据的比赛探测器 ThreadSanitizer 其工作得很好,在实践。
处理数据的比赛中一般需要一些编程的纪律,以诱使发生之前的边缘之间存取共享数据(或者在发展,或者他们一旦检测出使用上述工具)。这可以通过锁,条件变量、信号等等。然而,人们也可以采用不同程序的范式样的消息传递(而不是共享存储器),避免数据的种族通过建设。
排序的规范的定义是"当两个线程访问同一个位置存储在同一时间,至少一个访问是一个写." 在这种情况的"读卡"程的可获得旧的价值或新的价值,这取决于哪线"赢得了比赛。" 这并不总是一个错误—事实上,一些真正的毛茸茸的低级的算法,这样做的目的—但它一般应当是可以避免的。@史蒂夫Gury得到的一个很好的例子,当它可能是一个问题。
竞争条件,是一种错误,这种情况发生只有某些特定的时间条件。
例如:想象一下,你有两个线程,A和B。
在线程一:
if( object.a != 0 )
object.avg = total / object.a
在线B:
object.a = 0
如果螺的一个被抢占后,只是具有检查的对象。一个是"not null"、B会做 a = 0
, 和当螺纹一个将获得处理,它将做一个"分通过零"。
这个错误仅发生时,线被抢占的后果的声明,这是非常罕见的,但它可以发生。
竞争条件发生在多程应用程序或多处理系统。竞争条件,在其最基本的,是什么使假定这两个东西不在同一个线程或进程都将发生在一个特定的顺序,没有采取步骤,以确保他们这样做。发生这种情况通常在两个线传递消息的通过设置和检查员的变量的一类都可以访问。有几乎总是比赛的条件,当一个线电话睡得到另一个线程的时间来完成任务(除非该睡眠是在循环,某些检查机构)。
工具,用于防止比赛的条件是依赖于语言和操作系统,但有些起的是互斥、关键部分,并且信号。互斥是良好的,当你想要确定你是唯一一个在做什么。信号很好,当您想要确定其他人已完成在做的事情。最小化共享的资源也可以帮助防止出现意想不到的行为
检测竞争条件可能会很困难,但有一些迹象。代码,这在很大程度上依赖于睡容易发生种族的条件,因此,首先检查调睡在受影响的代码。增加特别长的睡也可用于调试试和部队一个特殊顺序的事件。这可能是有用的,用于再生的行为,看到如果你能让它消失了通过改变时间的事情,以及用于测试解决方案。该睡觉应该删除,后调试。
签名签名,一个具有竞争条件,但是,如果有一个问题,该问题只有间歇性地发生的一些机器。常见的错误将会崩溃和僵局。有记录的,你应该能够找到受影响地区的工作后从那里。
竞争条件是不仅有关的软件,但也有相关的硬件。实际上本期的最初创造的硬件产业。
根据 维基百科:
术语来源的想法 两个信号赛车每一个其他的 要 影响出第一.
竞争条件在一个逻辑电路:
软件行业采取了这个期限没有修改,这使得它有点难以理解。
你需要做一些更换为地图软件的世界:
- "两个信号"=>"两个线"/"两个进程"
- "影响的输出"=>"影响的一些共同国家"
所以竞争条件在软件行业的手段"的两个线"/"两个进程"的赛车每一个其它的"影响的一些共同国家",并最终结果的共享的国家,将取决于一些微妙的时间差,这可能会引起一些具体的线的/过程启动了,线/进程调度,等等。
竞争条件是局势并发的编程在两个并发线程或进程争取资源和所得的最终状态,取决于谁获得资源第一次。
微软其实已经出版了一个真的很详细 文章 在这个问题上的竞争条件和僵局。最抽象概括从这将是标题的段落:
竞争条件发生在两个线程访问一个共同变量 同一时间。第一个纹读取可变的,而第二 螺纹读取相同的价值变量。然后第一个线程 和第二线执行它们的操作上的价值,以及它们的比赛 来看看这线可以写的数值,最后的共同变量。值的线写它的价值最后被保留下来, 因为线是写作的价值,前线 写的。
什么是比赛的条件?
这种情况时,该进程是极为依赖的顺序或时间的其他活动。
例如, 处理器A和B处理器 两者都需要 相同资源为它们的执行。
你怎么检测到他们吗?
有的工具,以检测到比赛的条件自动:
你怎么处理他们?
竞争条件可能处理的 互斥的 或 信号灯.他们作为一个锁允许一个进程以获得资源,根据某些要求,以防止竞争条件。
你怎么阻止它们发生的?
有各种不同的方法来防止竞争条件,例如 关键的部分避免.
- 没有两个进程同时他们的内部关键地区。(互相排斥)
- 任何假设都是由有关的速度或数量的Cpu。
- 没有运行的进程以外的关键地区块的其他进程。
- 没有处理已经永远等待进入其关键的区域。(A等待B的资源,B等待C资源、C等待资源)
竞争条件是不希望的情况时出现的设备或系统的尝试进行两个或多个操作的同时,但因性质的设备或系统的操作必须在适当的顺序是正确的。
在计算机存储器或储存,的竞争条件可能会发生,如果命令阅读和书写大量的数据都收到几乎在同一时刻,而该机器试图复盖的一些或所有的旧数据的同时,旧的数据仍在阅读。结果可以是一个或更多的以下:一个电脑崩溃,一个"非法操作,"通知和关闭的程序错误读旧的数据,或者错误写的新的数据。
这是典型的银行帐户平衡的例子,这将有助于新成员了解程Java易w。r.t.比赛的条件:
public class BankAccount {
/**
* @param args
*/
int accountNumber;
double accountBalance;
public synchronized boolean Deposit(double amount){
double newAccountBalance=0;
if(amount<=0){
return false;
}
else {
newAccountBalance = accountBalance+amount;
accountBalance=newAccountBalance;
return true;
}
}
public synchronized boolean Withdraw(double amount){
double newAccountBalance=0;
if(amount>accountBalance){
return false;
}
else{
newAccountBalance = accountBalance-amount;
accountBalance=newAccountBalance;
return true;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
BankAccount b = new BankAccount();
b.accountBalance=2000;
System.out.println(b.Withdraw(3000));
}
试试这个基本的实例,用于更好地理解竞争条件:
public class ThreadRaceCondition {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Account myAccount = new Account(22222222);
// Expected deposit: 250
for (int i = 0; i < 50; i++) {
Transaction t = new Transaction(myAccount,
Transaction.TransactionType.DEPOSIT, 5.00);
t.start();
}
// Expected withdrawal: 50
for (int i = 0; i < 50; i++) {
Transaction t = new Transaction(myAccount,
Transaction.TransactionType.WITHDRAW, 1.00);
t.start();
}
// Temporary sleep to ensure all threads are completed. Don't use in
// realworld :-)
Thread.sleep(1000);
// Expected account balance is 200
System.out.println("Final Account Balance: "
+ myAccount.getAccountBalance());
}
}
class Transaction extends Thread {
public static enum TransactionType {
DEPOSIT(1), WITHDRAW(2);
private int value;
private TransactionType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
};
private TransactionType transactionType;
private Account account;
private double amount;
/*
* If transactionType == 1, deposit else if transactionType == 2 withdraw
*/
public Transaction(Account account, TransactionType transactionType,
double amount) {
this.transactionType = transactionType;
this.account = account;
this.amount = amount;
}
public void run() {
switch (this.transactionType) {
case DEPOSIT:
deposit();
printBalance();
break;
case WITHDRAW:
withdraw();
printBalance();
break;
default:
System.out.println("NOT A VALID TRANSACTION");
}
;
}
public void deposit() {
this.account.deposit(this.amount);
}
public void withdraw() {
this.account.withdraw(amount);
}
public void printBalance() {
System.out.println(Thread.currentThread().getName()
+ " : TransactionType: " + this.transactionType + ", Amount: "
+ this.amount);
System.out.println("Account Balance: "
+ this.account.getAccountBalance());
}
}
class Account {
private int accountNumber;
private double accountBalance;
public int getAccountNumber() {
return accountNumber;
}
public double getAccountBalance() {
return accountBalance;
}
public Account(int accountNumber) {
this.accountNumber = accountNumber;
}
// If this method is not synchronized, you will see race condition on
// Remove syncronized keyword to see race condition
public synchronized boolean deposit(double amount) {
if (amount < 0) {
return false;
} else {
accountBalance = accountBalance + amount;
return true;
}
}
// If this method is not synchronized, you will see race condition on
// Remove syncronized keyword to see race condition
public synchronized boolean withdraw(double amount) {
if (amount > accountBalance) {
return false;
} else {
accountBalance = accountBalance - amount;
return true;
}
}
}
你永远不要放弃竞争条件。如果你有一个标志,它可以阅读和书面通过多线程,以及这标志设定"完成"通过一个线程,以便其他线停止处理当标志设定"完成",你不想,"竞争条件"加以排除。事实上,这一可以被称作一种良性竞争条件。
然而,使用的工具,用于检测的比赛的条件下,它会发现,作为一种有害的竞争条件。
更多细节上的竞争条件在这里, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx.
考虑操作,它已以显示计数尽快数增加。即, 尽快 CounterThread 递增的价值 DisplayThread 需要显示最近更新的价值。
int i = 0;
输出
CounterThread -> i = 1
DisplayThread -> i = 1
CounterThread -> i = 2
CounterThread -> i = 3
CounterThread -> i = 4
DisplayThread -> i = 4
在这里, CounterThread 被锁定频繁和更新的价值之前 DisplayThread 显示。这里存在一个竞争条件。竞争条件可以解决通过使用Synchronzation
你可以 防止竞争条件, 如果您使用的"原子"的课程。其原因是仅有的线不独立的操作中得到并设置,例如下:
AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);
结果,你会有7个在链接"爱".虽然你没有两项行动,但是两个操作的,确认同一个线程和没有一个其他的线将干扰到这一点,这意味着没有比赛的条件下!
竞争条件是不希望出现的情况,当两个或多个过程可以访问和变化的共享数据,在同一时间。它的发生是因为有相互矛盾的访问到的资源。关键的部分问题可能导致竞争条件。为了解决关键的条件之中的过程中,我们已经采取了唯一一个过程的时间执行的关键部分。
public class Synchronized_RACECONDITION {
private static final int NUM_INCREMENTS = 10000;
private static int count = 0;
public static void main(String[] args) {
testSyncIncrement();
testNonSyncIncrement();
}
private static void testSyncIncrement() {
count = 0;
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, NUM_INCREMENTS)
.forEach(i -> executor.submit(Synchronized_RACECONDITION::incrementSync));
ConcurrentUtils.stop(executor);
System.out.println(" Sync: " + count);
}
private static void testNonSyncIncrement() {
count = 0;
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, NUM_INCREMENTS)
.forEach(i -> executor.submit(Synchronized_RACECONDITION::increment));
ConcurrentUtils.stop(executor);
System.out.println("NonSync: " + count);
}
private static synchronized void incrementSync() {
count = count + 1;
}
private static void increment() {
count = count + 1;
}
static class ConcurrentUtils {
public static void stop(ExecutorService executor) {
try {
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
System.err.println("termination interrupted");
}
finally {
if (!executor.isTerminated()) {
System.err.println("killing non-finished tasks");
}
executor.shutdownNow();
}
}
}
}