Нужен ли мне семафор при чтении из глобальной структуры?

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

Скажем, у нас есть глобальная структура (в 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];
};

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

Тот факт, что члены были целыми числами, и поэтому записи, вероятно, атомарны, в данном случае действительно является отвлекающим маневром.]

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

Ответы 10

Если все потоки только читают, вам не нужен семафор.

Если член read_only фактически доступен только для чтения, то нет опасности изменения данных и, следовательно, нет необходимости в синхронизации. Это могут быть данные, настроенные до запуска потоков.

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

Если данные читаются часто, но записываются нечасто, вы можете получить потокобезопасность и хорошую производительность, используя отдельные блокировки чтения и записи.

Adam Rosenfield 05.11.2008 19:40

Верно. Я уверен, что больше информации можно получить из многих источников. en.wikipedia.org/wiki/Readers-writer_lock

Jonathan Adelson 05.11.2008 19:43

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

Martin York 05.11.2008 21:26

Если LOCK является атомарным (то есть с одним целым числом), вам вообще не нужны блокировки мьютексов / фьютексов.

Tim Post 17.03.2009 15:07

Я бы спрятал каждое поле за вызовом функции. Поля только для записи будут иметь семафор. Только для чтения просто возвращает значение.

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

Adam Liss 06.11.2008 06:45

Я не имел в виду их «только для записи». Просто они, вероятно, будут часто меняться. Их непременно нужно прочитать. «Только для чтения» действительно только для чтения.

JXG 06.11.2008 10:30

Нет.

Как правило, вам нужны семафоры для предотвращения одновременного доступа к ресурсам (в данном случае это int). Однако, поскольку член read_only доступен только для чтения, он не будет меняться между / во время доступа. Обратите внимание, что это даже не обязательно атомарное чтение - если ничего не изменится, вы всегда в безопасности.

Как вы изначально устанавливаете read_only?

read_only устанавливается одним потоком один раз перед запуском остальных потоков.
JXG 05.11.2008 19:44

Добавление к предыдущим ответам:

  1. В этом случае естественной парадигмой синхронизации является взаимное исключение, а не семафоры.
  2. Я согласен с тем, что вам не нужны мьютексы для переменных только для чтения.
  3. Если часть структуры для чтения-записи имеет ограничения согласованности, в общем случае вам понадобится мьютекс один для все из них, чтобы операции оставались атомарными.

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

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

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


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

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

Хороший ответ. Если вам не нужна синхронизация между потоками, а операция атомарная, блокировка не требуется.

Martin York 05.11.2008 21:24

Перечитывая это, я пытаюсь представить алгоритм, который будет генерировать «даты, такие как 31 февраля». Хммм .... в то время звучало неплохо!

Adam Liss 17.03.2009 15:08

@ Сэм Хойс: Хороший момент! Фактически, если бы я просмотрел этот код, я бы отправился в класс корректирующих календарных алгоритмов.

Adam Liss 17.03.2009 21:26

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

sk. 17.03.2009 21:57

Читателям тоже нужны мьютексы!

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

Вот почему, в виде примера.

Представьте себе часы, которые обновляются каждую секунду с помощью кода:

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 относятся к записываемым переменным, поэтому, конечно, вам нужны примитивы синхронизации.

Tom Barta 06.11.2008 08:19

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

Adam Liss 06.11.2008 08:32

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

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

Большое спасибо всем замечательным ответчикам (и за все прекрасные ответы).

Подводить итоги:

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

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

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