Я пытаюсь написать программу на C++ с одной глобальной переменной, которая читается несколькими потоками и обновляется одним потоком. В этом случае мне нужно написать какой-либо семафор, например, вещи, или я могу просто оставить его как есть, поскольку только 1 поток фактически записывает в глобальную переменную, поэтому нет возможных условий гонки.
Также я новичок в семафоре, поэтому мне нужно избавить себя от хлопот, если это возможно.
Программа такая: тред писателя: постоянно проверять контакт на наличие высокого напряжения, устанавливать глобальную переменную, когда оно высокое
читательские потоки: постоянно проверять глобальную переменную в бесконечном цикле и что-то делать, когда она установлена.
Вам нужно будет предоставить больше контекста, но в целом вы должны защитить доступ к этой переменной, используя какой-то примитив синхронизации, такой как std :: mutex. Вы можете ослабить требования, если вам все равно, может ли какой-то поток прочитать неверное значение, но все зависит от вашего варианта использования.
Вы можете упростить и заменить «несколько читателей» в своем вопросе на «один читатель», и ответ все равно будет: вы должны защитить доступ к общей переменной из разных потоков, иначе поток читателей может не «наблюдать» изменение, которое было сделанные потоком записи и, по сути, заканчивают тем, что читают и используют неправильное значение.
Если это простое целое число, которое вы пытаетесь прочитать / изменить из ваших потоков, вы можете использовать std :: atomic.
Если вы не заботитесь о правильности, вы можете просто использовать volatile-спецификатор, но в этом случае поток «читатель» не будет «видеть» обновленное значение в течение некоторого времени. Сложно сказать без какой-либо примерной программы или контекста.
Я сделал некоторый контекст для просмотра моего нового сообщения, а затем вы можете сообщить мне
В вашем случае вы можете использовать std :: atomic, вы также можете просто использовать volatile, если вам все равно, что читатели могут не видеть сразу обновленное значение. Вы можете просто упростить все и просто использовать std :: mutex и сделать его действительно потокобезопасным.
Трудно сказать, не зная, что будет составлять программу верный в вашем конкретном случае. Но это правда, что вам не нужно беспокоиться о повреждении памяти, если у вас есть только один параллельный писатель.
Однако несколько считывателей получат неопределенные значения без блокировки. Вы определенно захотите использовать атомарные загрузки (скорее всего, seq_cst) и хранилища для значения, и вы можете изучить ключевое слово volatile
, чтобы предотвратить сохранение значения в регистре, если это вызывает беспокойство в вашем приложении.
volatile здесь совершенно не поможет.
@Pavel - Я не понимаю, почему ты так говоришь. Если глобальная переменная является пустым int, то передача ее в функцию, которая принимает ссылку volatile int, является разницей между перезагрузкой значения и его кешированием. См. Этот Godbolt для нескольких различных сценариев.
Кажется, по требованию OP было бы нормально использовать volatile (но тогда безопасность потоков вообще не задействована). Однако volatile
никоим образом не обеспечит потокобезопасность. Даже обычный атомарный объект может не обеспечить, это зависит от memory_order, который вы используете с атомиком.
Ваш Godbolt абсолютно показывает, что он не будет потокобезопасным :) Как только поток записывает в некоторую ячейку памяти, другие потоки могут не «наблюдать» изменение в течение некоторого времени (например, тысячи циклов). Только надлежащий барьер памяти может гарантировать синхронизацию памяти между потоками.
Извините, я не сказал, почему это «абсолютно видно», хотя на самом деле ваш опубликованный код показывает обратное, я изначально не проверял asm :) x86 имеет сильную модель памяти, поэтому это тот же код, сгенерированный при использовании volatile. Например, на ARM все будет иначе. Добавьте в пример компилятор ARM, и вы увидите.
"Но это правда, что вам не нужно беспокоиться о повреждении памяти, если у вас есть только один параллельный писатель." В каком стандарте я могу найти эту гарантию? И даже если бы это было правдой, как он мог знать, сколько у него писателей? Ничто в стандарте C++ не препятствует коду, который, кажется, только читает объект, от внутренней записи в него. (И это на самом деле произошло и фактически привело к поломке кода!)
Это просто: если более одного потока могут получить доступ к объекту одновременно без синхронизации, и хотя бы один из этих потоков выполняет запись в объект, программа имеет гонку данных. Поведение программы, имеющей гонку за данные, не определено.
Вы можете обеспечить синхронизацию, предотвратив одновременный доступ с помощью мьютекса или, во многих случаях, используя атомарный объект.
Если вы неправильно синхронизируете чтение и запись, вы можете насладиться популярной игрой «Угадай, что может сделать эта программа». Существует множество цепочек сообщений, которые объясняют, почему гонка за данные допустима при определенных обстоятельствах. Это нормально, если вам все равно, правильно ли работает ваша программа. Если вам не все равно, синхронизируйтесь.
хорошо, я использую атомарную в этом случае, потому что это проще
@bakalolo - да, это идеальный кандидат на атомарную переменную. Разработчик стандартной библиотеки для вашего целевого оборудования знает об оборудовании больше, чем вы или я, и сделает все необходимое, чтобы оно работало правильно.
Если записей не так много, решение атомарных переменных выполняется быстрее. Если это сильно изменится, то compareAndSwap (атомарная операция) вызовет много повторных попыток. Синхронизированные решения с мьютексами обычно разрешают доступ к переменной только одному потоку. В других языках программирования есть блокировки чтения и записи, которые, возможно, также доступны в стандартной библиотеке для C++. Можно было бы построить блокировку чтения-записи с мьютексом (но не делайте этого :-))
@thomas - я вижу, что в C++ 17 добавлен std :: shared_mutex, который говорит, что его можно использовать в ситуациях, когда много чтения и записи редко.
Это блокировка чтения-записи. У вас есть функция блокировки / разблокировки, а также функция lock_shared / unlock_shared. Общий означает, что несколько потоков могут получить доступ к защищенному ресурсу / переменной.
так ты говоришь, что мьютекс необязателен, но атомарен обязателен? Нет, меня не волнует, если значение иногда неверно