首页 文章

什么是比赛条件?

提问于
浏览
780

编写多线程应用程序时,遇到的最常见问题之一是竞争条件 .

我对社区的问题是:

什么是比赛条件?你怎么发现它们?你怎么处理它们?最后,你如何防止它们发生?

18 回答

  • 54

    当两个或多个线程可以访问共享数据并且他们尝试同时更改它时,会出现争用情况 . 因为线程调度算法可以在任何时间在线程之间交换,所以您不知道线程将尝试访问共享数据的顺序 . 因此,数据变化的结果取决于线程调度算法,即两个线程都“竞相”访问/改变数据 .

    当一个线程执行“check-then-act”时(例如“检查”,如果值为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
    
  • 0

    当访问共享资源的多线程(或其他并行)代码可能以导致意外结果的方式执行时,存在“竞争条件” .

    举个例子:

    for ( int i = 0; i < 10000000; i++ )
    {
       x = x + 1; 
    }
    

    如果您有5个线程同时执行此代码,则x WOULD NOT的值最终为50,000,000 . 事实上,每次运行都会有所不同 .

    这是因为,为了使每个线程增加x的值,它们必须执行以下操作:(简化,显然)

    Retrieve the value of x
    Add 1 to this value
    Store this value to x
    

    任何线程都可以随时在此过程中的任何步骤,并且当涉及共享资源时,它们可以相互踩踏 . 在读取x和写回x之间的时间内,x的状态可以由另一个线程改变 .

    让's say a thread retrieves the value of x, but hasn' t存储它 . 另一个线程也可以检索x的 same 值(因为还没有线程改变它),然后它们都将 same 值(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
    

    在访问共享资源的代码之前使用某种 locking 机制可以避免竞争条件:

    for ( int i = 0; i < 10000000; i++ )
    {
       //lock x
       x = x + 1; 
       //unlock x
    }
    

    在这里,答案每次都是50,000,000 .

    有关锁定的更多信息,请搜索:互斥锁,信号量,临界区,共享资源 .

  • 2

    什么是比赛条件?

    你计划在下午5点去看电影 . 您可以在下午4点查询门票的可用性 . 该代表说他们可以使用 . 您可以在演出前5分钟放松并到达售票窗口 . 我相信你可以猜到会发生什么:这是一个完整的房子 . 这里的问题是检查和行动之间的持续时间 . 你在4点询问并在5点采取行动 . 与此同时,其他人抢了票 . 这是一种竞争条件 - 特别是竞争条件下的“检查然后行动”情景 .

    你如何发现它们?

    宗教代码审查,多线程单元测试 . 没有捷径 . 这个Eclipse插件很少出现,但还没有稳定 .

    你如何处理和预防他们?

    最好的方法是创建无副作用和无状态函数,尽可能使用不可变的函数 . 但这并非总是可行的 . 因此,使用java.util.concurrent.atomic,并发数据结构,正确的同步和基于actor的并发将有所帮助 .

    最佳的并发资源是JCIP . 您还可以获得更多details on above explanation here .

  • 18

    以下是经典的银行账户余额示例,它将帮助新手轻松了解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));
    
    }
    
  • 8

    竞争条件和数据竞赛之间存在重要的技术差异 . 大多数答案似乎都假设这些术语是等价的,但事实并非如此 .

    当2个指令访问相同的存储器位置时发生数据竞争,这些访问中的至少一个是写入,并且在这些访问中进行排序之前没有发生 . 现在,在排序之前构成事件的内容会受到很多争论,但一般来说,同一个锁变量上的ulock-lock对和相同条件变量上的等待信号对会导致发生先发生顺序 .

    竞争条件是语义错误 . 这是导致错误的程序行为的事件的时间或顺序中发生的缺陷 .

    许多竞争条件可能(实际上是)由数据竞争引起,但这不是必需的 . 事实上,数据竞赛和竞争条件既不是必要的,也不是彼此的充分条件 . This博客文章也用一个简单的银行交易示例很好地解释了差异 . 这是另一个解释差异的简单example .

    现在我们确定了术语,让我们尝试回答原始问题 .

    鉴于竞争条件是语义错误,有没有检测它们的一般方法 . 这是因为在一般情况下,无法使用能够区分正确与不正确程序行为的自动化oracle . 种族检测是一个不可判定的问题 .

    另一方面,数据竞赛具有精确的定义,不一定与正确性相关,因此可以检测它们 . 有许多种类的数据竞争检测器(静态/动态数据竞争检测,基于锁定的数据竞争检测,基于发生的数据竞争检测,混合数据竞争检测) . 最先进的动态数据竞争检测器是ThreadSanitizer,在实践中非常有效 .

    处理数据竞争通常需要一些编程规则来诱导在共享数据访问之前发生 - 在开发期间或使用上述工具检测到它们之后 . 这可以通过锁,条件变量,信号量等来完成 . 但是,也可以采用不同的编程范例,如消息传递(而不是共享内存),以避免构造中的数据争用 .

  • 33

    一种规范的定义是“当两个线程同时访问内存中的同一位置时,至少有一个访问是写入." In the situation the " reader " thread may get the old value or the new value, depending on which thread "赢得了比赛 . ”这并不总是一个错误 - 事实上,一些真正有毛的低级算法是故意这样做的 - 但通常应该避免 . @Steve Gury给出了一个很好的例子,它可能是一个问题 .

  • 182

    竞争条件是一种只在某些时间条件下发生的错误 .

    示例:想象一下,您有两个线程,A和B.

    在线程A中:

    if( object.a != 0 )
        object.avg = total / object.a
    

    在线程B中:

    object.a = 0
    

    如果线程A在检查到object.a不为空之后被抢占,则B将执行 a = 0 ,并且当线程A将获得处理器时,它将执行"divide by zero" .

    只有当线程A在if语句之后被抢占时才会发生此错误,这种情况非常罕见,但它可能会发生 .

  • 0

    竞争条件发生在多线程应用程序或多进程系统中 . 最基本的竞争条件是假设两个不在同一个线程或过程中的事物将按特定顺序发生,而不采取措施确保它们发生的事情 . 当两个线程通过设置和检查类都可以访问的成员变量来传递消息时,通常会发生这种情况 . 当一个线程调用sleep以给另一个线程时间来完成一个任务时,几乎总是存在竞争条件(除非该休眠处于循环中,具有一些检查机制) .

    用于防止竞争条件的工具取决于语言和操作系统,但是一些常见的工具是互斥锁,关键部分和信号 . 当你想要确保你是唯一一个做某事的人时,互斥体是好的 . 当你想确保其他人已经完成某些事情时,信号很好 . 最小化共享资源还可以帮助防止意外行为

    检测竞争条件可能很困难,但有几个迹象 . 严重依赖睡眠的代码容易出现竞争条件,因此请首先检查受影响代码中的睡眠呼叫 . 添加特别长的睡眠也可以用于调试以尝试强制特定的事件顺序 . 这可以用于重现行为,看看是否可以通过改变事物的时间使其消失,以及测试解决方案 . 调试后应该删除睡眠 .

    但是,如果存在仅在某些机器上间歇性发生的问题,那么签名符号就会出现竞争条件 . 常见的错误是崩溃和死锁 . 通过日志记录,您应该能够找到受影响的区域并从那里开始工作 .

  • 5

    竞争条件不仅与软件有关,而且与硬件有关 . 实际上这个术语最初是由硬件行业创造的 .

    根据wikipedia

    该术语起源于两个信号相互竞争以首先影响输出的想法 . 逻辑电路中的竞争条件:

    理解这个术语的软件行业没有修改,这使得它有点难以理解 .

    您需要进行一些替换以将其映射到软件世界:

    • "two signals" => "two threads" / "two processes"

    • "influence the output" => "influence some shared state"

    因此,软件行业的竞争条件意味着“两个线程”/“两个进程”相互竞争“影响某些共享状态”,共享状态的最终结果将取决于一些微妙的时序差异,这可能是由某些特定的线程/进程启动顺序,线程/进程调度等

  • 0

    微软实际上已经在竞争条件和死锁问题上发布了一个非常详细的article . 其中摘要最多的摘要是 Headers 段落:

    当两个线程同时访问共享变量时,会出现争用情况 . 第一个线程读取变量,第二个线程从变量读取相同的值 . 然后第一个线程第二个线程对值执行操作,并且它们竞争以查看哪个线程可以将值最后写入共享变量 . 保留最后写入其值的线程的值,因为该线程正在写入前一个线程写入的值 .

  • 973

    竞争条件是并发编程的情况,其中两个并发线程或进程以及由此产生的最终状态取决于谁首先获取资源 .

  • 2

    竞争条件是当设备或系统试图同时执行两个或多个操作时发生的不期望的情况,但是由于设备或系统的性质,操作必须以正确的顺序进行以便成为做得对 .

    在计算机存储器或存储器中,如果几乎在同一时刻接收到读取和写入大量数据的命令,则机器会尝试覆盖部分或全部旧数据,而旧数据仍在读 . 结果可能是以下一种或多种:计算机崩溃,“非法操作”,程序通知和关闭,读取旧数据的错误或写入新数据的错误 .

  • 0

    好的,这4个问题 . 一个接一个的回答是......

    什么是比赛条件?

    当进程的输出和/或结果严重依赖于其他事件的顺序或时间时,例如, 2个信号正在争先恐后地改变输出 .

    你如何发现它们?

    它导致难以定位的错误 .

    你如何处理它们?

    使用信号量

    最后,

    你如何防止它们发生?

    避免竞争条件的一种方法是使用锁定机制来获取资源 . 但锁定资源可能会导致死锁 . 必须处理 .

  • 30

    尝试这个基本示例以更好地了解竞争条件:

    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;
            }
        }
    }
    
  • 2

    你并不总是想放弃竞争条件 . 如果你有一个可以被多个线程读取和写入的标志,并且这个标志被一个线程设置为'done',那么当flag设置为'done'时其他线程停止处理,你不希望那个“比赛”条件“被淘汰 . 事实上,这个可以被称为良性竞争条件 .

    但是,使用检测竞争条件的工具,它将被视为有害的竞争条件 .

    关于种族状况的更多细节,http://msdn.microsoft.com/en-us/magazine/cc546569.aspx .

  • 118

    考虑一旦计数增加就必须显示计数的操作 . 即, 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来解决Race Condition

  • 0

    如果使用"Atomic"类,则可以防止竞争条件 . 原因只是线程没有单独的操作get和set,例如如下:

    AtomicInteger ai = new AtomicInteger(2);
    ai.getAndAdd(5);
    

    结果,您将在链接“ai”中有7个 . 虽然你做了两个动作,但是这两个操作都确认了同一个线程并且没有其他线程会干扰这个,这意味着没有竞争条件!

  • 12

    竞争条件是两个或多个进程可以同时访问和更改共享数据时发生的不良情况 . 发生这种情况是因为存在对资源的冲突访问 . 关键部分问题可能会导致竞争状况 . 为了解决过程中的关键条件,我们一次只取出一个执行临界区的过程 .

相关问题