← Назад ко всем вопросам

Что будет при обращении двух потоков к одному участку памяти

1️⃣ Как кратко ответить

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

2️⃣ Подробное объяснение темы

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

Пример состояния гонки

Рассмотрим простой пример, где два потока увеличивают одно и то же значение в памяти:

#include <iostream>
#include <thread>
​
int counter = 0; // Общая переменная для всех потоков
​
void increment() {
    for (int i = 0; i < 1000; ++i) {
        ++counter; // Увеличиваем значение на 1
    }
}
​
int main() {
    std::thread t1(increment); // Создаем первый поток
    std::thread t2(increment); // Создаем второй поток
​
    t1.join(); // Ожидаем завершения первого потока
    t2.join(); // Ожидаем завершения второго потока
​
    std::cout << "Counter: " << counter << std::endl; // Выводим значение счетчика
    return 0;
}

В этом примере два потока одновременно увеличивают значение переменной counter. Ожидается, что итоговое значение будет 2000, но из-за состояния гонки результат может быть меньше.

Почему это происходит

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

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

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

Пример с использованием мьютекса

#include <iostream>
#include <thread>
#include <mutex>
​
int counter = 0; // Общая переменная для всех потоков
std::mutex mtx; // Мьютекс для синхронизации доступа
​
void increment() {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> lock(mtx); // Блокируем мьютекс
        ++counter; // Увеличиваем значение на 1
    } // Мьютекс автоматически разблокируется при выходе из области видимости
}
​
int main() {
    std::thread t1(increment); // Создаем первый поток
    std::thread t2(increment); // Создаем второй поток
​
    t1.join(); // Ожидаем завершения первого потока
    t2.join(); // Ожидаем завершения второго потока
​
    std::cout << "Counter: " << counter << std::endl; // Выводим значение счетчика
    return 0;
}

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

Заключение

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

Тема: Многопоточность / Синхронизация
Стадия: Tech

🔒 Подпишись на бусти автора и стань Алигатором, чтобы получить полный доступ к функционалу сайта и отслеживать свой прогресс!

Твои заметки