Multithreading 什么是比赛条件?
在编写多线程应用程序时,最常见的问题之一是竞争条件 我向社会提出的问题是: 比赛条件是什么?Multithreading 什么是比赛条件?,multithreading,concurrency,terminology,race-condition,Multithreading,Concurrency,Terminology,Race Condition,在编写多线程应用程序时,最常见的问题之一是竞争条件 我向社会提出的问题是: 比赛条件是什么? 如何检测它们? 您如何处理这些问题? 最后,如何防止它们发生 竞争条件是一种bug,只有在特定的时间条件下才会发生 例如: 假设你有两条线,A和B 在线程A中: if(object.a!=0) object.avg=总计/object.a 在线程B中: object.a=0 如果线程A在检查该对象后被抢占。A不为空,B将执行A=0,当线程A获得处理器时,它将执行“除以零” 只有当线程A在if语句之后
如何检测它们?
您如何处理这些问题?
最后,如何防止它们发生 竞争条件是一种bug,只有在特定的时间条件下才会发生 例如: 假设你有两条线,A和B 在线程A中:
if(object.a!=0)
object.avg=总计/object.a
在线程B中:
object.a=0
如果线程A在检查该对象后被抢占。A不为空,B将执行A=0
,当线程A获得处理器时,它将执行“除以零”
只有当线程A在if语句之后被抢占时,才会发生此错误,这是非常罕见的,但也可能发生。当两个或多个线程可以访问共享数据并试图同时更改数据时,就会发生争用情况。因为线程调度算法可以随时在线程之间交换,所以您不知道线程尝试访问共享数据的顺序。因此,数据更改的结果取决于线程调度算法,即两个线程都在“竞相”访问/更改数据 当一个线程执行“检查然后执行”(例如,如果值为X,则执行“检查”,然后执行“执行”以执行取决于值为X的某项操作),而另一个线程对介于“检查”和“执行”之间的值执行某项操作时,通常会出现问题。例如: 关键是,y可以是10,也可以是任何值,这取决于在检查和act之间是否有另一个线程更改了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
竞态条件是并发编程中的一种情况,其中两个并发线程或进程竞争一个资源,结果的最终状态取决于谁首先获得资源。竞态条件出现在多线程应用程序或多进程系统中。最基本的竞争条件是,假设两件不在同一线程或进程中的事情将按特定顺序发生,而不采取措施确保它们发生。当两个线程通过设置和检查都可以访问的类的成员变量来传递消息时,通常会发生这种情况。当一个线程调用sleep以给另一个线程完成任务的时间时,几乎总是存在竞争条件(除非该睡眠处于循环中,并带有某种检查机制) 防止竞争条件的工具依赖于语言和操作系统,但有些是互斥体、关键部分和信号。当你想确保你是唯一一个做某事的人时,互斥锁是很好的。当你想确保其他人完成某件事时,信号是好的。最小化共享资源也有助于防止意外行为 检测比赛情况可能很困难,但有几个迹象。严重依赖睡眠的代码容易出现争用情况,因此首先检查受影响代码中的睡眠调用。添加特别长的睡眠时间也可用于调试,以尝试和强制特定的事件顺序。这对于复制行为、查看是否可以通过更改事件的时间使其消失,以及测试落实到位的解决方案非常有用。调试后应删除休眠
然而,一个人有竞争条件的标志是,如果有一个问题只在某些机器上间歇性地发生。常见的错误是崩溃和死锁。通过日志记录,您应该能够找到受影响的区域并从那里返回。一种规范定义是“当两个线程同时访问内存中的同一位置,并且至少有一个访问是写入的。”在这种情况下,“读卡器”线程可能会获得旧值或新值,具体取决于哪个线程“赢得比赛。”这并不总是一个错误。事实上,一些真正毛茸茸的低级算法是故意这样做的,但通常应该避免。@Steve Gury给出了一个很好的例子,说明什么时候可能会出现问题。多线程(或并行)时存在“比赛条件”访问共享资源的代码可能会导致意外结果 举个例子:
for(int i=0;i<10000000;i++)
{
x=x+1;
}
如果您有5个线程同时执行此代码,那么x的值最终不会是50000000。事实上,它会随着每次运行而变化
这是因为,为了使每个线程增加x的值,它们必须执行以下操作:(显然是简化的)
检索x的值
将1添加到此值
将此值存储到x
任何线程都可以在任何时间处于该过程的任何步骤,并且在涉及共享资源时,它们可以相互踩踏。在读取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, value is 7
Thread 1: stores 8 in x
Thread 2: adds 1 to x, value is now 8
Thread 2: stores 8 in x
线程1:读取x,值为7
线程1:将1添加到x,值现在为8
线程2:读取x,值为7
线程1:在x中存储8
线程2:将1添加到x,值现在为8
线程2:在x中存储8
通过在访问共享资源的代码之前采用某种锁定机制,可以避免竞争条件:
for(int i=0;i<10000000;i++)
{
//锁x
x=x+1;
//解锁x
}
在这里,答案来了
Retrieve the value of x
Add 1 to this value
Store this value to x
Thread 1: reads x, value is 7
Thread 1: add 1 to x, value is now 8
Thread 2: reads x, value is 7
Thread 1: stores 8 in x
Thread 2: adds 1 to x, value is now 8
Thread 2: stores 8 in x
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;
}
}
}
int i = 0;
CounterThread -> i = 1
DisplayThread -> i = 1
CounterThread -> i = 2
CounterThread -> i = 3
CounterThread -> i = 4
DisplayThread -> i = 4
AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);