Ответ
Да, все потоки, созданные в рамках одного процесса, имеют доступ к глобальным (статическим) переменным, так как они находятся в общем адресном пространстве. Однако такой несинхронизированный доступ приводит к состоянию гонки (race condition). Для безопасной работы с общими данными необходимо использовать механизмы синхронизации.
Основные подходы в C++:
- Мьютексы (
std::mutex): Гарантируют эксклюзивный доступ. - Атомарные операции (
std::atomic): Позволяют выполнять операции чтения-записи без блокировок для примитивных типов. - Условные переменные (
std::condition_variable): Для координации потоков по событиям.
Пример с std::mutex и RAII-оберткой std::lock_guard:
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
int global_counter = 0;
std::mutex global_counter_mutex;
void increment_global(int iterations) {
for (int i = 0; i < iterations; ++i) {
// Автоматически блокирует и разблокирует мьютекс
std::lock_guard<std::mutex> lock(global_counter_mutex);
++global_counter; // Критическая секция защищена
}
}
int main() {
std::thread t1(increment_global, 100000);
std::thread t2(increment_global, 100000);
t1.join();
t2.join();
// Без мьютекса результат был бы меньше 200000
std::cout << "Final counter value: " << global_counter << std::endl; // 200000
return 0;
}
Почему это важно: Без синхронизации операция ++global_counter (которая состоит из чтения, изменения и записи) может быть прервана другим потоком, что ведет к потере обновлений и неопределенному поведению программы.
Ответ 18+ 🔞
Да ты представь, сидишь ты такой, пишешь код, и тут тебе в голову приходит гениальная идея — запустить несколько потоков, чтобы всё быстрее считалось. И вроде всё логично: потоки в одном процессе, значит, все глобальные переменные — это как общий холодильник на всю коммуналку. Захотел — взял, захотел — положил.
А потом начинается ёперный театр. Один поток только прочитал значение, второй уже своё туда впендюрил, первый обратно пишет по старой памяти — и всё, приехали. Состояние гонки, блядь. Результат получается такой, что хоть волком вой. Один говорит — 150 тысяч, другой — 180, а по факту нихуя не сходится. Удивление пиздец, честно.
Вот смотри, в чём суть. Чтобы не было этой пиздопроебибны, нужно эти общие данные как-то контролировать. В C++ для этого есть несколько штук, без которых — просто манда с ушами.
Первое — мьютексы (std::mutex). Это как ключ от сортира на вокзале. Пока один держит — остальные в очереди стоят и бздят. Но чтобы не забыть ключ в замке (разблокировать), умные люди придумали RAII-обёртки. std::lock_guard — это твой верный оруженосец. Вошёл в критическую секцию — он мьютекс взял, вышел — сам отпустил. Красота.
Второе — атомарные операции (std::atomic). Это для тех, кому мьютексы — как костыли для чемпиона по бегу. Для простых типов (int, bool) можно делать операции чтения-записи так, чтобы они были неразрывными. Без всей этой возни с блокировками. Быстро и чётко.
Третье — условные переменные (std::condition_variable). Это когда потоки должны не просто данные делить, а ещё и друг друга ждать. Типа «я сделал свою часть, сплю, разбуди, когда ты закончишь». Сложнее, но иногда без них — никуда.
Вот тебе наглядный пример, как не надо и как надо:
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
int global_counter = 0; // Наш общий «холодильник»
std::mutex global_counter_mutex; // Ключ от этого холодильника
void increment_global(int iterations) {
for (int i = 0; i < iterations; ++i) {
// Сейчас войдёт lock_guard и скажет: "Стоять! Разрешение есть только у меня!"
std::lock_guard<std::mutex> lock(global_counter_mutex);
++global_counter; // Вот эта строчка — священная корова. К ней только по одному.
}
}
int main() {
// Запускаем двух работяг
std::thread t1(increment_global, 100000);
std::thread t2(increment_global, 100000);
t1.join();
t2.join();
// А теперь смотрим, не обосрались ли они в общий счётчик
std::cout << "Final counter value: " << global_counter << std::endl; // Должно быть 200000
return 0;
}
А без мьютекса что? Операция ++global_counter — она же не атомарная, ёпта! Она ж три шага: прочитать значение, увеличить его на единицу, записать обратно. Пока один поток это делает, второй может влезть со своим чтением, и всё, пиши пропало. Значения потеряются, программа будет вести себя как хитрая жопа — вроде работает, а результат ебать копать какой-то левый.
Так что, чувак, доверия к потокам — ноль ебать. Либо блокировки, либо атомики. Иначе будет тебе не многопоточность, а гомосеки налетели, и все лезут в одну дырку одновременно.