Нужна ли блокировка, когда в общую переменную записывается только один поток? - PullRequest
9 голосов
/ 13 мая 2009

У меня 2 темы и общая float глобальная. Один поток только пишет в переменную, а другой только читает из нее, мне нужно заблокировать доступ к этой переменной? Другими словами:

volatile float x;

void reader_thread() {
    while (1) {
        // Grab mutex here?
        float local_x = x;
        // Release mutex?
        do_stuff_with_value(local_x);
    }
}

void writer_thread() {
    while (1) {
        float local_x = get_new_value_from_somewhere();
        // Grab mutex here?
        x = local_x;
        // Release mutex?
    }
}

Моя главная проблема заключается в том, что загрузка или хранилище float не являются атомарными, так что local_x в reader_thread в итоге имеет поддельное, частично обновленное значение.

  1. Это действительная проблема?
  2. Есть ли другой способ гарантировать атомарность без мьютекса?
  3. Будет ли работать sig_atomic_t в качестве разделяемой переменной, при условии, что для моих целей в ней достаточно битов?

Речь идет о языке C с использованием pthreads.

Ответы [ 7 ]

13 голосов
/ 13 мая 2009

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

Кроме того, поведение volatile не очень четко определено ... Спецификация использует его как способ предотвращения оптимизации удаленного доступа к вводу-выводу устройства с отображенной памятью, но ничего не говорит о его поведении на любом другие обращения к памяти.

Короче говоря, даже если нагрузки и хранилища являются атомарными на float x, я бы использовал явные барьеры памяти (хотя и зависит от платформы и компилятора) вместо зависимости от volatile. Без гарантии, что нагрузки и хранилища являются атомарными, вы бы имели бы для использования блокировок, которые подразумевают барьеры памяти.

5 голосов
/ 13 мая 2009

В соответствии с разделом 24.4.7.2 документации библиотеки GNU C:

На практике вы можете предположить, что int и другие целочисленные типы не более int являются атомарными. Вы также можете предположить, что типы указателей являются атомарными; это очень удобно. Оба эти предположения верны на всех машинах, которые поддерживает библиотека GNU C, и на всех известных нам системах POSIX.

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

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

3 голосов
/ 13 мая 2009

Назначение не является атомарным, по крайней мере для некоторых компиляторов, и в том смысле, что для его выполнения требуется одна инструкция. Следующий код был сгенерирован Visual C ++ 6.0 - f1 и f2 имеют тип float.

4:        f2 =  f1;
00401036   mov         eax,dword ptr [ebp-4]
00401039   mov         dword ptr [ebp-8],eax
3 голосов
/ 13 мая 2009

Я бы заблокировал это. Я не уверен, насколько велик float в вашей среде, но он не может быть прочитан / записан в одной инструкции, чтобы ваш читатель мог прочитать половинное значение. Помните, что volatile ничего не говорит об атомарности операций, просто говорится, что чтение будет происходить из памяти , а не кэшироваться в регистре или чем-то в этом роде.

1 голос
/ 13 мая 2009

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

Мой ответ был не очень полезен. gcc -msoft-float, возможно, является лишь особым случаем, когда нагрузки и запасы поплавков не являются атомарными.

0 голосов
/ 13 мая 2009

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

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

Реальный вопрос, я думаю, состоит в двух вопросах: "в чем минус мьютекса?" и "Каковы будут последствия, если я прочитаю мусор?"

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

0 голосов
/ 13 мая 2009

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

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

...