Расскажи про race condition

Состояние гонки (race condition) - ошибка проектирования многопоточной системы или приложения, при которой эта работа напрямую зависит от того, в каком порядке выполняются потоки. Состояние гонки возникает, когда поток, который должен исполнится в начале, проиграл гонку и первым исполняется другой поток: поведение кода изменяется, из-за чего возникают недетерменированные ошибки.

Oct. 21, 2023, Источник

Race condition (условие гонки) — это ситуация в многопоточной или распределенной системе, когда порядок выполнения операций влияет на результат работы программы, и этот порядок не может быть гарантирован. То есть, результат выполнения программы становится непредсказуемым из-за соревнования между потоками за доступ к общим ресурсам.

Часто возникают в следующих случаях:

  • Доступ к общим данным: Если два или более потока читают и пишут в одну и ту же переменную без должной синхронизации, конечное значение переменной может зависеть от того, в каком порядке потоки выполняют свои операции.
  • Зависимость от порядка выполнения: Когда корректность выполнения программы зависит от порядка выполнения операций между потоками, без явного контроля этого порядка.

Пример:

public class Counter {
    private int count = 0;

    public void increment() {
        count++; // Несмотря на кажущуюся атомарность, операция не атомарна и состоит из трех шагов: чтение, инкремент, запись
    }

    public int getCount() {
        return count;
    }
}

public class RaceConditionDemo {
    public static void main(String[] args) throws InterruptedException {
        final Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(counter.getCount()); // Ожидаемый результат 2000, но может быть меньше из-за условия гонки
    }
}

В этом примере, хотя каждый поток пытается увеличить счетчик на 1000, итоговый результат может быть меньше 2000 из-за них. Это происходит потому, что операция инкремента (`count++`) не атомарна и может быть прервана между чтением значения `count` и записью обновленного значения обратно в память. В результате, несколько потоков могут прочитать одно и то же значение `count` перед тем, как другие потоки успеют его обновить.

Решение проблемы

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

public synchronized void increment() {
    count++;
}

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

Feb. 27, 2024, easyoffer