Что такое состояние гонки?

При написании многопоточных приложений одна из наиболее распространенных проблем - это условия гонки.

Мои вопросы для сообщества:

Что такое состояние гонки? Как вы их обнаруживаете? Как вы справляетесь с ними? Наконец, как вы их предотвращаете?

Ответ 1

Условие гонки возникает, когда два или более потока могут обращаться к общим данным, и они пытаются изменить его в одно и то же время. Поскольку алгоритм планирования потоков может меняться между потоками в любое время, вы не знаете порядок, в котором потоки будут пытаться получить доступ к общим данным. Следовательно, результат изменения данных зависит от алгоритма планирования потока, то есть оба потока являются "гоночными" для доступа/изменения данных.

Часто возникают проблемы, когда один поток выполняет "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

Ответ 2

Условие "гонки" существует, когда многопоточный (или иначе параллельный) код, который будет обращаться к общему ресурсу, может сделать это таким образом, чтобы вызвать неожиданные результаты.

Возьмем этот пример:

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, но еще не сохранил его. Другой поток также может получить значение same x (потому что ни один нить еще не изменил его), а затем они оба будут хранить значение same (x + 1) в х!

Пример:

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

Условия гонки можно избежать, используя механизм блокировки до кода, который обращается к общему ресурсу:

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

Здесь ответ приходит как 50 000 000 каждый раз.

Для получения дополнительной информации о блокировке выполните поиск: мьютекс, семафор, критический раздел, общий ресурс.

Ответ 3

Что такое состояние гонки?

Вы планируете перейти в кино в 17:00. Вы спрашиваете о наличии билетов в 16:00. Представитель говорит, что они доступны. Вы расслабляетесь и добираетесь до окна билета за 5 минут до шоу. Я уверен, что вы можете догадаться, что происходит: это полный дом. Проблема здесь заключалась в продолжительности между проверкой и действием. Вы спросили 4 и выступили в 5. В то же время кто-то еще схватил билеты. Это условие гонки - в частности сценарий "проверки-действия-действия" условий гонки.

Как вы их обнаруживаете?

Религиозный обзор кода, многопоточные модульные тесты. Нет ярлыка. На этом есть плагин Eclipse, но ничего стабильного пока нет.

Как вы обрабатываете и предотвращаете их?

Лучше всего было бы создавать свободные побочные эффекты и функции без гражданства, максимально использовать неизменяемые. Но это не всегда возможно. Таким образом, используя java.util.concurrent.atomic, помогут параллельные структуры данных, правильная синхронизация и основанный на актерах concurrency.

Лучшим ресурсом для concurrency является JCIP. Вы также можете получить более подробную информацию здесь..

Ответ 4

Существует важное техническое различие между условиями гонки и гонками данных. Большинство ответов, похоже, делают предположение, что эти термины эквивалентны, но они не являются.

Голосование данных происходит, когда 2 команды обращаются к одному и тому же месту памяти, по крайней мере один из этих обращений является записью, и нет необходимости до упорядочивания между этими обращениями. Теперь то, что происходит, происходит до заказа, подвержено большому количеству дебатов, но в целом пары блокировки улоков на одной и той же переменной блокировки и пар сигнала ожидания при одной и той же переменной условия вызывают случайный порядок.

Состояние гонки - это семантическая ошибка. Это недостаток, связанный с синхронизацией или упорядочением событий, что приводит к ошибочному поведению программы.

Многие условия гонки могут (и на самом деле) вызваны гонками данных, но это необязательно. На самом деле, расы данных и условия гонки не являются ни необходимым, ни достаточным условием для друг друга. Это сообщение в блоге также объясняет разницу очень хорошо, с простым примером банковских транзакций. Вот еще один простой пример , который объясняет разницу.

Теперь, когда мы применили терминологию, попробуем ответить на исходный вопрос.

Учитывая, что условия гонки являются семантическими ошибками, нет общего способа их обнаружения. Это связано с тем, что нет способа автоматизированного оракула, который может отличить правильное и неправильное поведение программы в общем случае. Обнаружение гонки - неразрешимая проблема.

С другой стороны, расы данных имеют точное определение, которое не обязательно связано с правильностью, и поэтому их можно обнаружить. Существует множество разновидностей детекторов гонки данных (статическое/динамическое обнаружение гонки данных, обнаружение гонки данных на основе блокировки, обнаружение на основе данных на основе ранее обнаруженных данных, обнаружение гибридных данных). Современный динамический детектор гонки данных ThreadSanitizer, который на практике работает очень хорошо.

Обработка расчётов данных в общем требует некоторой дисциплины программирования, чтобы вызвать это - перед ребрами между обращениями к общим данным (либо во время разработки, либо после их обнаружения с использованием вышеупомянутых инструментов). это можно сделать с помощью блокировок, переменных условий, семафоров и т.д. Однако можно также использовать разные парадигмы программирования, такие как передача сообщений (вместо общей памяти), которые предотвращают расследование данных по построению.

Ответ 5

Сортоканоническое определение: "когда два потока одновременно обращаются в одно и то же место в памяти, и по крайней мере один из них - это запись". В ситуации поток читателя может получить старое значение или новое значение, в зависимости от того, какой поток "выигрывает гонку". Это не всегда ошибка: на самом деле некоторые действительно волосатые алгоритмы низкого уровня делают это специально, но этого, как правило, следует избегать. @Steve Gury дает хороший пример того, когда это может быть проблемой.

Ответ 6

Состояние гонки - это своего рода ошибка, которая происходит только с определенными временными условиями.

Пример: Представьте, что у вас есть два потока: A и B.

В потоке A:

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

В потоке B:

object.a = 0

Если поток A выгружен сразу после проверки того, что object.a не является нулевым, B будет делать a = 0, а когда поток A получит процессор, он будет "делить на ноль".

Эта ошибка возникает только тогда, когда поток A выгружается сразу после инструкции if, это очень редко, но это может произойти.

Ответ 7

Условия гонки происходят в многопоточных приложениях или многопроцессорных системах. Условие гонки, в его самом базовом, - это все, что делает предположение, что две вещи не в одном потоке или процессе не будут выполняться в определенном порядке, не предпринимая шагов для обеспечения их выполнения. Обычно это происходит, когда два потока передают сообщения, устанавливая и проверяя переменные-члены класса, к которым могут обращаться. Почти всегда существует условие гонки, когда один поток вызывает сон, чтобы дать другое время для завершения задачи (если этот сон не находится в цикле с некоторым механизмом проверки).

Инструменты для предотвращения условий гонки зависят от языка и ОС, но некоторые из них - это мьютексы, критические разделы и сигналы. Мьютексы хороши, когда вы хотите убедиться, что вы единственный, кто что-то делает. Сигналы хороши, когда вы хотите убедиться, что кто-то еще закончил что-то делать. Минимизация общих ресурсов также может помочь предотвратить неожиданное поведение

Обнаружение условий гонки может быть затруднительным, но есть пара знаков. Код, который в значительной степени зависит от сна, склонен к условиям гонки, поэтому сначала проверьте, чтобы вызовы спать в затронутом коде. Добавление особенно длинных снов также может быть использовано для отладки, чтобы попытаться заставить определенный порядок событий. Это может быть полезно для воспроизведения поведения, если вы можете заставить его исчезнуть, изменив время и вещи для тестирования. Сны должны быть удалены после отладки.

Знак подписи, что у одного есть условие гонки, есть, если есть проблема, которая возникает только периодически на некоторых машинах. Общими ошибками были бы сбои и взаимоблокировки. С протоколированием вы сможете найти пострадавший район и вернуться оттуда.

Ответ 8

Состояние гонки связано не только с программным обеспечением, но и с аппаратным обеспечением. На самом деле термин первоначально был придуман аппаратной отраслью.

Согласно википедии:

Этот термин связан с идеей двух сигналов, ведущих друг к другу, чтобы влиять на выход в первую очередь.

Состояние гонки в логической схеме:

enter image description here

Программная индустрия приняла этот термин без изменений, что делает его немного трудным для понимания.

Вам нужно сделать некоторую замену, чтобы отобразить ее в мире программного обеспечения:

  • "два сигнала" => "два потока"/"два процесса"
  • "влиять на выход" => "влиять на какое-то общее состояние"

Таким образом, состояние гонки в индустрии программного обеспечения означает "два потока"/"два процесса", которые участвуют друг в друге, "влияют на какое-то общее состояние", а конечный результат общего состояния будет зависеть от некоторой тонкой разницы во времени, что может быть вызвано некоторыми конкретными порядок запуска потоков/процессов, планирование потоков/процессов и т.д.

Ответ 9

Условие состязания - это ситуация при параллельном программировании, когда два параллельных потока или процесса конкурируют за ресурс, и итоговое конечное состояние зависит от того, кто получит ресурс первым.

Ответ 10

Microsoft действительно опубликовала действительно подробную статью по этому вопросу условий гонки и тупиков. Наиболее суммарным из них будет абзац заголовка:

Условие гонки возникает, когда два потока обращаются к общей переменной при в то же время. Первый поток читает переменную, а второй thread считывает одно и то же значение из переменной. Затем первая нить и вторая нить выполняет свои операции над значением, и они участвуют в гонке чтобы увидеть, какой поток может записать значение, последнее для общей переменной. Значение потока, которое записывает свое значение last, сохраняется, потому что поток пишет над значением, которое предыдущий поток написал.

Ответ 11

Вот классический пример баланса банковского счета, который поможет новичкам понять потоки в 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));

}

Ответ 12

Условие гонки является нежелательной ситуацией, когда устройство или система пытаются выполнить две или более операции одновременно, но из-за характера устройства или системы операции должны выполняться в правильной последовательности в чтобы сделать это правильно.

В памяти компьютера или в хранилище может возникнуть условие гонки, если команды для чтения и записи большого количества данных принимаются почти в одно и то же мгновение, и машина пытается перезаписать некоторые или все старые данные, а старые данные все еще читается. Результатом может быть одно или несколько из следующего: компьютерный сбой, "незаконная операция", уведомление и завершение работы программы, ошибки чтения старых данных или ошибки записи новых данных.

Ответ 13

Что такое состояние гонки?

Ситуация, когда процесс критически зависит от последовательности или времени других событий.

Например, процессору A и процессору B необходимы идентичные ресурсы для их выполнения.

Как вы их обнаруживаете?

Есть инструменты для автоматического определения состояния гонки:

Как вы справляетесь с ними?

Состояние гонки может быть обработано с помощью Mutex или Semaphores. Они действуют как блокировка, позволяющая процессу получать ресурсы на основе определенных требований для предотвращения состояния гонки.

Как вы предотвращаете их появление?

Существуют различные способы предотвращения состояния расы, такие как предотвращение критических участков.

  1. Нет двух процессов одновременно внутри их критических областей. (Взаимное исключение)
  2. Не делается никаких предположений о скорости или количестве процессоров.
  3. Ни один процесс не работает за пределами своей критической области, которая блокирует другие процессы.
  4. Никакой процесс не должен ждать вечно, чтобы войти в его критическую область. (A ждет ресурсов B, B ждет ресурсов C, C ждет ресурсов A)

Ответ 14

Попробуйте этот базовый пример для лучшего понимания состояния гонки:

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

Ответ 15

Вы не всегда хотите отказаться от состояния гонки. Если у вас есть флаг, который может быть прочитан и написан несколькими потоками, и этот флаг установлен на "done" одним потоком, так что другая обработка прекращения потока, когда флаг установлен на "done", вы не хотите, чтобы "гонка" условие "будет устранено. Фактически, это можно назвать доброкачественным состоянием гонок.

Однако, используя инструмент для обнаружения состояния гонки, он будет замечен как опасное состояние гонки.

Подробнее о состоянии гонки здесь, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx.

Ответ 16

Рассмотрим операцию, которая должна отображать счет, как только счет будет увеличен. т.е., как только 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

Ответ 17

Вы можете предотвратить состояние гонки, если используете классы "Atomic". Причина в том, что поток не разделяет операции get и set, пример ниже:

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

В результате у вас будет 7 в ссылке "ai". Хотя вы сделали два действия, но обе операции подтверждают тот же поток, и ни один другой поток не будет вмешиваться в это, это означает отсутствие условий гонки!

Ответ 18

Условие гонки является нежелательной ситуацией, которая возникает, когда два или более процесса могут одновременно получать доступ и изменять совместно используемые данные. Это произошло из-за конфликта доступа к ресурсу. Проблема критического сечения может привести к состоянию гонки. Для решения критического состояния процесса мы вынимаем только один процесс за раз, который выполняет критический раздел.

Ответ 19

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();
        }
    }
}
}