а. При наличии глобальной переменной в многопоточном приложении каждый поток имеет свою собственную «копию» глобальной переменной по соображениям производительности, или каждый поток может читать/записывать только одну и ту же единственную версию глобальной переменной.
б. Если ответ первый, то мьютекс Lock(), за которым следует Unlock(), гарантирует копирование данных в соответствующую глобальную переменную.
Я разговаривал с OpenAI, и они настаивали на том, что существует только одна версия глобальной переменной и что все конфликты — это логические/гоночные проблемы, которые нужно решать с помощью синхронизации.
Примечание: язык Cpp. Компилятор является компилятором VS по умолчанию. ОС — винда. Целевой процессор — современный 5+ ядер.
Изменить. Многие комментарии предполагают, что если не указано иное, глобальная переменная имеет только одну копию во всем запущенном экземпляре приложения. Это круто. Позвольте мне теперь добавить этот код и спросить, является ли этот код полностью безопасным и нет ли необходимости в какой-либо синхронизации или любом другом механизме защиты доступа к данным. [Это будет псевдокод С++, а не собственный исполняемый код]. Имейте также в виду, что это не мой фактический код передовой практики, я просто пытаюсь убедиться в определенных концепциях в рамках расследования.
//In MyApplication.cpp
int Variable1 = 0;
bool ThreadAFinished = false;
void ThreadAFunction()
{
while (true)
{
Variable1++;
if (Variable1 == 1000)
{
break;
}
}
ThreadAFinished = true;
}
void ThreadBFunction()
{
while (true)
{
if (ThreadAFinished)
{
if (Variable1 < 1000)
{
printf("ERROR");
}
else
{
printf("TEST PASSED");
}
break;
}
}
}
В этом случае мы гарантируем, что ни разу не возникнет ошибка?
Другие уже прокомментировали: вы можете полагаться на то, что все потоки будут иметь доступ только к одной копии, если вы явно не сделаете ее локальной для потока.
Что касается проблем с гонкой потоков, имейте в виду, что вас обманет не только компилятор, оптимизирующий другой порядок инструкций, но и кэширование памяти и регистров. Не ждите, что модификатор volatile решит эти проблемы... он также не гарантирует правильного поведения во всех ситуациях.
В Википедии есть хорошие (короткие и понятные) статьи о ограждении памяти/барьере памяти, а в стандартных библиотеках также есть кое-что для вас: ищите «Библиотека атомарных операций»
Это ценный ответ, но я отредактирую свой вопрос и добавлю конкретный код, чтобы убедиться, что я не ошибаюсь.
@Physician: «Позвольте мне теперь добавить этот код и спросить, есть ли ...» - Stack Overflow НЕ является чатом. Вы задали вопрос, а потом получили ответ. После этого в вопросе разрешены только те правки, которые улучшают его (исправление грамматики, исправление стилистических проблем и т. д.). Вы НЕ должны редактировать сообщение с вопросом, чтобы изменить его спецификацию вопроса или добавить новые вопросы в сообщение. Если у вас есть еще вопросы, создайте новый пост с вопросами. (Но убедитесь, что вы искали тему, о которой хотите спросить. Например, «С++ доступ к глобальной переменной из нескольких потоков»).
Может быть несколько копий одной и той же глобальной переменной. Каждый процессор имеет свой собственный кэш данных, и кэш обычно содержит отдельную копию любых данных, которые использует процессор.
Это одна из причин, по которой вам необходимо обеспечить синхронизацию между потоками, обращающимися к одним и тем же данным; один поток может записывать данные в свой локальный кеш, а другой поток читает «те же самые» данные либо из своего собственного локального кеша, либо из памяти, и не видит изменений, сделанных другим потоком.
Код в вопросе имеет гонку данных: один поток обновляет глобальные переменные, а другой поток считывает значения глобальных переменных. Поведение программы не определено. Самое простое решение — изменить типы двух глобальных переменных, чтобы сделать их атомарными:
std::atomic<int> Variable1 = 0;
std::atomic<bool> ThreaedAFinished = false;
Теперь компилятор (точнее, библиотека времени выполнения) предоставит соответствующий код, гарантирующий, что кэши очищаются и перезагружаются надлежащим образом, так что две глобальные переменные действуют так, как будто существует только одна копия каждой.
... имеет ли каждый поток свою собственную "копию" глобальной переменной...
Да, но нет.
«Да», потому что на низком уровне, на уровне, лежащем ниже всего, что связано с языком C++, обновления переменной разными потоками могут кэшироваться в разных местах памяти.
«Нет», потому что «кэш» не является частью объяснения того, как работает язык C++. Он не является частью модели памяти языка.
Как объясняет модель памяти, существует одна и только одна копия любой данной глобальной переменной. Но когда разные потоки обновляют глобальные переменные без явной синхронизации, тогда потокам разрешено не соглашаться с порядком, в котором происходили обновления разных переменных. Это несогласие может позволить потокам видеть несогласованные, иногда поврежденные представления общих данных, но также позволяет системе наиболее эффективно использовать аппаратные кэши и получать наилучшую производительность, когда потоки обращаются к своим личным данным.
гарантирует ли мьютекс Lock(), за которым следует Unlock(), копирование данных в глобальную переменную?
Блокировка и разблокировка мьютексов — это один из видов явной синхронизации, которую программа может использовать для обеспечения того, чтобы разные потоки видели согласованные представления одной и той же общей структуры данных.
Если у вас есть какая-то общая глобальная структура, и если каждый поток блокирует один и тот же общий глобальный мьютекс всякий раз, когда они обращаются к этой структуре, тогда структура безопасна. Что означает «безопасный», так это то, что каждый раз, когда поток A блокирует мьютекс, он будет видеть структуру данных в том же самом точном состоянии, в котором ее оставил какой-то другой поток непосредственно перед тем, как другой поток освободил мьютекс.
Однако если какой-либо поток обращается к структуре без блокировки мьютекса, даже если это доступ только для чтения, другой поток может увидеть структуру в несогласованном состоянии, потому что разные части того, что он видит, могут поступать из разных уровней «кеша».
Так что ответ нет. Простое использование Lock(), Unlock() не гарантирует глобального видимого редактирования переменных. Только использование одного и того же мьютекса каждым потоком для доступа к этой глобальной переменной действительно поможет? Я прав?
@Врач, верно. Все потоки должны блокировать один и тот же мьютекс при доступе к одним и тем же общим данным.
Это именно то, о чем я спрашивал. Спасибо за вашу помощь. Хорошего дня.
Комментарии перемещены в чат ; пожалуйста, не продолжайте обсуждение здесь. Прежде чем публиковать комментарий под этим, пожалуйста, ознакомьтесь с целями комментариев . Комментарии, которые не требуют разъяснений или предложений по улучшению, обычно относятся к ответу , к Meta Stack Overflow или в чату переполнения стека. Комментарии, продолжающие обсуждение, могут быть удалены.