Действительно ли глобальные переменные являются глобальными в их поведении при совместном использовании ОЗУ?

а. При наличии глобальной переменной в многопоточном приложении каждый поток имеет свою собственную «копию» глобальной переменной по соображениям производительности, или каждый поток может читать/записывать только одну и ту же единственную версию глобальной переменной.

б. Если ответ первый, то мьютекс 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;
            }
        }
    }

В этом случае мы гарантируем, что ни разу не возникнет ошибка?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
112
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

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

В Википедии есть хорошие (короткие и понятные) статьи о ограждении памяти/барьере памяти, а в стандартных библиотеках также есть кое-что для вас: ищите «Библиотека атомарных операций»

Это ценный ответ, но я отредактирую свой вопрос и добавлю конкретный код, чтобы убедиться, что я не ошибаюсь.

Physician 19.04.2023 14:12

@Physician: «Позвольте мне теперь добавить этот код и спросить, есть ли ...» - Stack Overflow НЕ является чатом. Вы задали вопрос, а потом получили ответ. После этого в вопросе разрешены только те правки, которые улучшают его (исправление грамматики, исправление стилистических проблем и т. д.). Вы НЕ должны редактировать сообщение с вопросом, чтобы изменить его спецификацию вопроса или добавить новые вопросы в сообщение. Если у вас есть еще вопросы, создайте новый пост с вопросами. (Но убедитесь, что вы искали тему, о которой хотите спросить. Например, «С++ доступ к глобальной переменной из нескольких потоков»).

Tsyvarev 19.04.2023 19:17

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

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

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

std::atomic<int> Variable1 = 0;
std::atomic<bool> ThreaedAFinished = false;

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

Ответ принят как подходящий

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

Да, но нет.

«Да», потому что на низком уровне, на уровне, лежащем ниже всего, что связано с языком C++, обновления переменной разными потоками могут кэшироваться в разных местах памяти.

«Нет», потому что «кэш» не является частью объяснения того, как работает язык C++. Он не является частью модели памяти языка.

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

гарантирует ли мьютекс Lock(), за которым следует Unlock(), копирование данных в глобальную переменную?

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

Если у вас есть какая-то общая глобальная структура, и если каждый поток блокирует один и тот же общий глобальный мьютекс всякий раз, когда они обращаются к этой структуре, тогда структура безопасна. Что означает «безопасный», так это то, что каждый раз, когда поток A блокирует мьютекс, он будет видеть структуру данных в том же самом точном состоянии, в котором ее оставил какой-то другой поток непосредственно перед тем, как другой поток освободил мьютекс.

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

Так что ответ нет. Простое использование Lock(), Unlock() не гарантирует глобального видимого редактирования переменных. Только использование одного и того же мьютекса каждым потоком для доступа к этой глобальной переменной действительно поможет? Я прав?

Physician 19.04.2023 20:44

@Врач, верно. Все потоки должны блокировать один и тот же мьютекс при доступе к одним и тем же общим данным.

Solomon Slow 19.04.2023 20:46

Это именно то, о чем я спрашивал. Спасибо за вашу помощь. Хорошего дня.

Physician 19.04.2023 20:47

Другие вопросы по теме