Мьютексы вокруг чтения и записи одного слова

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


person Jeff Lamb    schedule 28.01.2011    source источник


Ответы (2)


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

Чтобы увеличить переменную на единицу — по крайней мере, в большинстве архитектур, с которыми я имею дело в эти дни — вы должны прочитать значение из памяти в регистр, настроить значение в регистре и записать его обратно в память. Предположим, у нас есть 2 потока, с высоким приоритетом A и с низким приоритетом B. Возьмем такой сценарий:

  • Поток B считывает «x» из памяти в регистр (значение равно 5).
  • Поток B увеличивает регистр до значения 6.
  • Поток A вытесняет поток B.
  • Поток A считывает «x» в регистр (значение по-прежнему 5 в памяти, верно?).
  • Поток A увеличивает регистр до значения 6.
  • Поток A записывает значение 6 обратно в память
  • Поток A переходит в спящий режим.
  • Поток B просыпается и записывает значение своего регистра 6 обратно в память.

Теперь оба потока увеличили значение, но оно увеличилось только на единицу.

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

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

Примечание. В некоторых случаях — но не делайте этого — вы можете обойтись без мьютекса, если у вас есть один писатель и несколько читателей. Например, может быть, ISR увеличивает счетчик тиков или что-то еще, и другие потоки/задачи приходят и читают его время от времени.

Думаю, я хочу подчеркнуть, что всегда полезно защищать общие данные, даже если вы думаете, что можете обойтись без них. Если вы хотите «стать коммандос», вам ДЕЙСТВИТЕЛЬНО нужно знать, что вы делаете, и даже в этом случае тот же код может сломаться завтра, когда он будет портирован на новую архитектуру.

person Dan    schedule 28.01.2011
comment
Это и было главной темой обсуждения в нашей группе. Тем не менее, я не думаю, что защита от чтения/записи вашей переменной будет здесь иметь значение. Вам нужно будет сделать «взять», «прочитать», «увеличить», «записать», «дать», вынув мьютекс из ваших функций чтения/записи, чтобы сделать это. Если вы 'берете' 'читаете' 'даете' 'приращаете' 'берете' 'записываете' 'даете', помещая мьютекс в свои функции чтения и записи, возникает та же проблема вытеснения, если она вытесняется до или после вашей операции 'приращения'. Я думаю, что это всегда будет проблемой с двумя писателями, и именно поэтому я задал этот вопрос. - person Jeff Lamb; 28.01.2011
comment
Да, нужно обязательно взять-прочитать-изменить-записать-дать. Другой шаблон, как вы заметили, просто не работает. Чтение-изменение-запись должны рассматриваться как одна транзакция и защищаться как единое целое. Если ваш код имеет отдельные вызовы чтения и записи, вы работаете на неправильном уровне абстракции/инкапсуляции для общего ресурса. Это необходимо решить, возможно, добавив отдельный вызов модификации, если вам нужно сохранить вызов простого чтения, или путем рефакторинга каким-либо образом, подходящим для конкретного приложения. - person Erik Johnson; 02.03.2011

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

Для получения дополнительной информации см. статью Википедии о барьере памяти.

person Mike Daniels    schedule 28.01.2011
comment
Это происходит только при включенной оптимизации? Читая эту ссылку, кажется, что ключевое слово volatile в данных полностью предотвратит это, устраняя необходимость в мьютексе. Это одноядерный процессор, поэтому проблем с многоядерностью здесь нет. Я знаю, что многословные переменные (массивы, двойные числа, строки) требуют мьютексов, но я все еще не уверен, что доступ к одному слову необходим. - person Jeff Lamb; 28.01.2011