2012-04-25 2 views
2

В многопоточной или RTOS-среде эти коды ниже идентичны?Компилятор связан - эти два кода C действительно идентичны?

Я считаю, что это не так. Но является ли 1-й код абсолютным сохранением в многопоточной среде? Есть ли правило для компилятора для назначения регистра для «ga» и не будет читать «ga» позже в func_a()?

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

// ga - глобальная переменная.

int func_a() { 

    int a = ga; 
    return a>2 ? a-2 : 2-a; 
} 

int func_b() { 

    return ga>2 ? ga-2 : 2-ga; 
} 

Мое намерение ищет стандартным способом (не конкретной платформы) только один раз прочитать га и присваивает его значение локальной переменной «а».

«a» может использоваться последовательно, независимо от того, изменился ли параметр «ga».

+0

ли RTOS упреждающий? – RedX

+0

Хех! Хорошая точка, но давайте предположим, что это так. –

+0

Да, предположим, что это упреждающее, а также ISR может изменить «ga». – user184258

ответ

2

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

+0

Тогда какой надежный способ получить «моментальный снимок» глобальной переменной, а затем играть с ним, не заботясь о том, было ли обновлено его значение или нет? - за исключением использования блокировки. – user184258

+0

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

+0

Что произойдет, если этой функции был передан адрес ga в параметре и он должен был разыменовать его? Это что-нибудь изменит? –

0

поведение зависит от того, какой компилятор вы используете, у каждого компилятора есть свои правила относительно оптимизации.

+0

Также может быть и аппаратное поведение, а не только специфическое для компилятора. –

2

В стандарте C нет правила, требующего от компилятора реализовать эти функции по-разному. например При работе с регистрами компилятор может или не может «оптимизировать» присвоение от ga до a (т. Е. «Оптимизировать», я имею в виду: загрузите ga в REG, затем используйте тот же REG для выполнения остальных вычислений , используя его как a). Или это может не так.

Если вы хотите реализовать блокировки свободной структуры данных:

  1. C99 не предлагает ничего, что может помочь вам.
  2. C11 (очень современный стандарт) предлагает вам атомные типы данных.

Если вы используете C99, то вам необходимо либо:

  1. Используйте замки (и, следовательно, не безблокировочного код)
  2. Будьте готовы к написанию архитектуры определенный код. Самое меньшее, что вам нужно сделать, это использовать минимальный набор атомных операций, как это сделано в this library, который реализует блокирующие данные структуры данных с использованием атомных операций, предоставляемых x86, x86_64 и ARM ISAs.

В более ранней версии этого ответа, я коснулся стороны вопрос в (который имеет отношение к volatile, и которая на самом деле не имеет отношения к вашему реальному вопроса):

Eсти один случай, который может поставить ограничение на то, как реализовано func_b, но я фактически ухожу по касательной здесь: Если ga объявлен как volatile.

Если ga летуч, то каждый читал на gaдолжны нагрузки ga из памяти заново. то есть в func_b, ga будет загружаться из памяти два раза. Один раз для сравнения, и один раз, чтобы вычислить возвращаемое значение. Ожидаемое использование, например, означает, что ga относится к порту ввода/вывода с отображением памяти. Затем, если значение ga изменяется между двумя считанными, это отразится на возвращаемом значении. Однако, если вы измените ga в другом потоке, не ожидайте нормального/определенного поведения.

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

+0

Я понимаю использование неустойчивого. Мой вопрос скорее о противоположности того, что «летучие» намереваются достичь. Я хочу читать «ga» только один раз и использовать его значение после этого, не читая его снова, чтобы не получить противоречивый результат (например, a> 2? A-2: 2-a) – user184258

+2

@ user184258: Нет абсолютно никакой гарантии в будет ли ваш скомпилированный код иметь одно чтение ga или многих, и будет ли это чтение атомарным. Извините, языковой стандарт не гарантирует вам ничего здесь. Это может работать так, как вы хотите, на некоторых процессорах и с некоторыми компиляторами, но не нужно и на других процессорах, а с другими компиляторами этого не будет. –

+0

@Alex +1. Правильно. Кроме того: я отредактировал ответ, чтобы уточнить, что «не имея изменчивости» не гарантирует ни одного чтения. – ArjunShankar

0

Два фрагмента, вероятно, будут иметь идентичный машинный код. Ни один из них не является безопасным в многопоточном случае.

принудительно создает временную переменную, но поскольку копия из ga в переменную volatile не гарантируется быть атомарной, это не является потокобезопасным.

Единственный безопасный способ писать подобный код, с охранниками:

int func_a() { 

    mtx_lock(&ga_mutex); 
    int a = ga; 
    mtx_unlock(&ga_mutex); 

    return a>2 ? a-2 : 2-a; 
} 
+0

Не нужно ли все же оставаться неустойчивым? Как теперь компилятор не сможет переместить его чтение до/после операций mutex? –

+0

@Alex Нет, 'ga' не обязательно должен быть изменчивым. Компилятор мог бы знать это многими способами, но это не ваша проблема, это проблема того, кто проектировал операции мьютекса и кто бы ни строил компилятор. Он должен * знать как-то, иначе операции мьютекса будут нарушены. (Один из способов, который обычно делается, - это встроенный встроенный в mutex специальный компилятор, называемый [clobber] (http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html#ss5. 3) На некоторых платформах он «просто работает», потому что компилятор не может считать, что сами операции mutex не изменяют 'ga'. Это зависит от платформы.) –

+0

@Alex Compilers могут изменять порядок, в котором код если это может доказать, что заказ не влияет на результат. Он может делать такую ​​оптимизацию, чтобы дать лучший инструктаж, предсказание ветвей и так далее. В многопоточной многоядерной среде такая оптимизация может быть опасной, если одно ядро ​​делает предположения и решает кэшировать инструкции до того, как код будет выполнен, то другое ядро ​​изменяет значение. Чтобы предотвратить такие ошибки, необходимо использовать концепцию [барьеров памяти] (http://en.wikipedia.org/wiki/Memory_barrier). (продолжение ->) – Lundin

Смежные вопросы