Довольно простой вопрос, но я не вижу, чтобы его нигде задавали.
Скажем, у нас есть глобальная структура (в C), например:
struct foo {
int written_frequently1;
int read_only;
int written_frequently2;
};
Мне кажется очевидным, что если у нас много потоков чтения и записи, нам понадобится семафор (или другая блокировка) на членах written_frequently, даже для чтения, поскольку мы не можем быть на 100% уверены, что назначения этой структуре будут атомный.
Если мы хотим, чтобы множество потоков читало член read_only и ни одного - для записи, нам нужен семафор в доступе к структуре только для чтения?
(Я склонен сказать нет, потому что тот факт, что местоположения непосредственно перед и после постоянно меняются, не должен влиять на член read_only, а несколько потоков, считывающих значение, не должны мешать друг другу. Но я не уверен .)
[Edit: теперь я понимаю, что мне следовало задать этот вопрос гораздо лучше, чтобы прояснить очень конкретно, что я имел в виду. Естественно, я не особо разбирался во всех проблемах, когда впервые задал вопрос. Конечно, если я сейчас полностью отредактирую вопрос, я испорчу все эти прекрасные ответы. То, что я имел в виду, больше похоже на:
struct bar {
char written_frequently1[LONGISH_LEN];
char read_only[LONGISH_LEN];
char written_frequently2[LONGISH_LEN];
};
Основная проблема, о которой я спросил, заключается в том, что, поскольку эти данные являются частью структуры, влияют ли они на них вообще со стороны других членов структуры и могут ли они повлиять на них взамен?
Тот факт, что члены были целыми числами, и поэтому записи, вероятно, атомарны, в данном случае действительно является отвлекающим маневром.]





Если все потоки только читают, вам не нужен семафор.
Если член read_only фактически доступен только для чтения, то нет опасности изменения данных и, следовательно, нет необходимости в синхронизации. Это могут быть данные, настроенные до запуска потоков.
Вам потребуется синхронизация любых данных, которые можно записать, независимо от частоты.
Верно. Я уверен, что больше информации можно получить из многих источников. en.wikipedia.org/wiki/Readers-writer_lock
Пока запись является атомарной (т.е. всего один int), вам вообще не нужны блокировки.
Если LOCK является атомарным (то есть с одним целым числом), вам вообще не нужны блокировки мьютексов / фьютексов.
Я бы спрятал каждое поле за вызовом функции. Поля только для записи будут иметь семафор. Только для чтения просто возвращает значение.
Семафор не мешает читателю увидеть «снимок», когда запись сделана наполовину. Семафор означает, что «значения меняются и могут быть несовместимыми», поэтому читатели должны уважать их так же, как писателей.
Я не имел в виду их «только для записи». Просто они, вероятно, будут часто меняться. Их непременно нужно прочитать. «Только для чтения» действительно только для чтения.
Нет.
Как правило, вам нужны семафоры для предотвращения одновременного доступа к ресурсам (в данном случае это int). Однако, поскольку член read_only доступен только для чтения, он не будет меняться между / во время доступа. Обратите внимание, что это даже не обязательно атомарное чтение - если ничего не изменится, вы всегда в безопасности.
Как вы изначально устанавливаете read_only?
read_only устанавливается одним потоком один раз перед запуском остальных потоков.
Добавление к предыдущим ответам:
«Только для чтения» немного вводит в заблуждение, поскольку переменная записывается по крайней мере один раз при инициализации. В этом случае вам по-прежнему нужен барьер памяти между начальной записью и последующими чтениями, если они находятся в разных потоках, иначе они могли бы увидеть неинициализированное значение.
Вам нужен мьютекс, чтобы гарантировать атомарность операции. Итак, в этом конкретном случае вы может вообще не нуждаться в мьютексе. В частности, если каждый поток записывает в элемент одини запись является атомарной и, новое значение не зависит от текущего значения элемента Любые (включая его самого), проблем нет.
Пример: каждый из нескольких потоков обновляет переменную last_updated_by, которая просто записывает последний поток, который его обновил. Ясно, что до тех пор, пока сама переменная обновляется атомарно, ошибок возникать не будет.
Однако вам делать нужен мьютекс, чтобы гарантировать согласованность, если поток читает или пишет имеет более одного элемента за раз, особенно потому, что вы упоминаете блокировку элемент, а не вся структура.
Пример: - поток обновляет элементы «день», «месяц» и «год» в структуре. Это должно происходить атомарно, чтобы другой поток не прочитал структуру после приращения «месяца», но до того, как «день» перейдет в 1, чтобы избежать таких дат, как 31 февраля. Обратите внимание, что вы должны честь мьютекса при чтении; в противном случае вы можете прочитать ошибочное наполовину обновленное значение.
Хороший ответ. Если вам не нужна синхронизация между потоками, а операция атомарная, блокировка не требуется.
Перечитывая это, я пытаюсь представить алгоритм, который будет генерировать «даты, такие как 31 февраля». Хммм .... в то время звучало неплохо!
@ Сэм Хойс: Хороший момент! Фактически, если бы я просмотрел этот код, я бы отправился в класс корректирующих календарных алгоритмов.
В вашем примере «последнее обновление кем» есть проблема - записи могут быть переупорядочены, если они не синхронизированы, то есть значение, записанное в поле последнего обновления, может быть перезаписано более ранним значением.
Похоже, существует распространенное заблуждение, что мьютексы предназначены только для писателей и что читателям они не нужны. Это не правильно,, и это заблуждение отвечает за ошибки, которые являются чрезвычайно сложно диагностировать.
Представьте себе часы, которые обновляются каждую секунду с помощью кода:
if (++seconds > 59) { // Was the time hh:mm:59?
seconds = 0; // Wrap seconds..
if (++minutes > 59) { // ..and increment minutes. Was it hh:59:59?
minutes = 0; // Wrap minutes..
if (++hours > 23) // ..and increment hours. Was it 23:59:59?
hours = 0; // Wrap hours.
}
}
Если код не защищен мьютексом, другой поток может читать переменные hours, minutes и seconds во время обновления. Следуя приведенному выше коду:
[Start just before midnight] 23:59:59 [WRITER increments seconds] 23:59:60 [WRITER wraps seconds] 23:59:00 [WRITER increments minutes] 23:60:00 [WRITER wraps minutes] 23:00:00 [WRITER increments hours] 24:00:00 [WRITER wraps hours] 00:00:00
Время недействительно с первого приращения до последней операции шесть шагов спустя. Если читатель проверит часы в течение этого периода, он увидит не только неправильное значение, но и значение незаконный. И поскольку ваш код, скорее всего, будет зависеть на часах без отображение времени напрямую, это классический источник ошибок «рикошета», которые, как известно, трудно отследить.
Окружите код обновления часов мьютексом и создать функцию чтения, который также блокирует мьютекс пока он выполняется. Теперь читатель будет ждать завершения обновления, а писатель не изменит значения во время чтения.
Я не думаю, что вы понимаете, о чем спрашивал ОП. Он имеет в виду переменные, которые вообще не записываются (предположительно, после запуска программы). В вашем примере все seconds/minutes/hours относятся к записываемым переменным, поэтому, конечно, вам нужны примитивы синхронизации.
Я понимаю, что OP задает очень конкретный вопрос об особом случае, но я обеспокоен тем, что некоторые ответы подразумевают, что читателю не нужен мьютекс в Общее, что неверно и приводит к особенно неприятным ошибкам. Ясно, что количество, которое никогда не меняется, является потокобезопасным.
Возможно, вам понравится прочитать любую из этих статей на практическое программирование без блокировки или просто проанализировать и понять предоставленные фрагменты.
Большое спасибо всем замечательным ответчикам (и за все прекрасные ответы).
Подводить итоги:
Если есть член структуры, доступный только для чтения (в нашем случае, если значение установлено один раз, задолго до того, как какой-либо поток может захотеть его прочитать), то потокам, читающим этот член, не нужны блокировки, мьютексы, семафоры или любые другие другая защита параллелизма.
Это верно, даже если другим участникам часто пишут. Тот факт, что разные переменные являются частью одной и той же структуры, не имеет значения.
Если данные читаются часто, но записываются нечасто, вы можете получить потокобезопасность и хорошую производительность, используя отдельные блокировки чтения и записи.