2010-04-26 3 views
0

Как предотвратить состояние гонки БЕЗ блокировки или использования мьютексов/семафоров в C++? Я имею дело с вложенным циклом, в котором я буду заходящим значение в массиве:Как предотвратить состояние гонки БЕЗ использования блокировок на C++?

for (int i = 0; i < m; ++i) 
    for (int j = 0; j < n; ++j) 
    for (int k = 0; k < o; ++k) 
     array[k] += foo(...); 

Более или менее, я хочу иметь дело с этим, так что я могу обеспечить различные темы, работаю одновременно не записывать в массив [k] одновременно. Любые предложения о том, как подойти к этому?

Редактировать: Я бегу на машине Linux, и мне также нужно использовать компилятор Intel. Я буду использовать «icc» вместо «gcc» для компиляции кода.

+0

какая ОС? или вы используете ускорительную библиотеку? – Naveen

+0

все это в Linux. – Hristo

+1

Я немного смущен. В заголовке вы запрашиваете решение без блокировок и в вопросе, который вы хотите заблокировать? – Burkhard

ответ

3

Предполагая, что окна и что array содержит элементы типа LONG вы могли бы сделать что-то вроде:

for (int i = 0; i < m; ++i) 
    for (int j = 0; j < n; ++j) 
     for (int k = 0; k < o; ++k) { 
      LONG val = foo(...); 
      InterlockedAdd(&array[k], val); 
     } 

Если вы не работаете в Windows, платформы могут иметь подобный набор API. Пока ваша платформа имеет тип API InterlockedCompareExchange(), вы можете написать свою собственную версию InterlockedAdd().

Что-то вроде следующей (оговорки - непроверенное):

int InterlockedAdd(int volatile* pDest, int operand) 
{ 
     int curval = *pDest; 
     int oldval; 

     do { 
      oldval = curval; 
      curval = InterlockedCompareExchange(pDest, oldval + operand, oldval); 
     } while (curval != oldval); 

     return oldval+operand; 
} 

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

Есть некоторые портативные библиотеки, которые поддерживают атомное сравнения и обмена и другие атомарные операции, как документированных части интерфейса:

Также обратите внимание, что C++ 0x будет поддерживать атомные операции, такие как compa re/exchange - я не уверен, какой уровень поддержки находится в текущих компиляторах C++ (он не выглядит как VS 2010).

+1

Знаете ли вы, есть ли функция повышения для InterlockedAdd или InterlockedCompareExchange? – Inverse

+0

Отсутствует закрывающая фигурная скобка на петле do while. – titaniumdecoy

+0

Исправлена ​​скобка, добавлено немного о различной библиотечной поддержке для атомных операций. –

2

Предполагая, что массив содержит целые числа, используйте gcc atomic builtins. __sync_fetch_and_add должен сделать трюк.

+0

для этой конкретной программы, я буду использовать компилятор Intel. Я не уверен, что это означает, что я не могу использовать «gcc's atomic builtins» – Hristo

4

Для этого конкретного контура поверните его наизнанку. Положите k снаружи, то вы можете поместить k=0 в другую тему, чем k=1 и так далее.

Пока foo() не зависит от массива [k], где k! = Это ток k, тогда вы золотой.

+0

Мне нравится ваша идея. Однако я не совсем понимаю. Что вы подразумеваете под «Пока foo() не зависит от массива [k], где k! = It current k"? – Hristo

+0

Следите за [ложным обменом] (http://en.wikipedia.org/wiki/False_sharing). Кэш может работать против вас. –

+0

О, я просто имею в виду, что если 'foo()' зависит от 'array [0]', когда он пытается вычислить 'array [1]', тогда у вас будут проблемы с более сложной синхронизацией, чем просто использование Marcelo's предложение или что-то в этом роде. – sblom

0

Разделите самую внутреннюю петлю между нитями. Thread T1 обрабатывает индексы в диапазоне [0, L), потоки T2 обрабатывают индексы в диапазоне [L, 2L) и т. Д. L = o/n, где n - количество потоков. Это предполагает, что вызов foo() не использует другие местоположения, которые могут быть вычислены одновременно.

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

+0

Поскольку я пытаюсь повысить производительность для этой программы, использование взаимосвязанных операций - это не очень хорошая идея. – Hristo

0

Самый простой способ (хотя и не самый эффективный!) - обернуть цикл в «критический» раздел.

См. Здесь Wikipedia. Это ограничит цикл кода одним потоком в любой момент времени.

2

То, как вы этого хотите, не может быть сделано! (см. комментарий sbi)

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

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

0

с использованием ВЗАИМОСВЯЗАННЫХ операций, а других предположили, даст правильного результата, но это может ухудшить производительность плохо. (Если внутренний цикл короток, многие темы будут конкурируют за несколько ячеек памяти, , которые будут эффективно сериализующей программой .) Ссылка | флаг

Не правда, мой друг. На самом деле блокированные операции превосходят все виды блокировки. Точнее: все объекты синхронизации (критические разделы, мьютексы, события и т. Д.), Безусловно, реализованы с точки зрения взаимосвязанных операций. Фактически блокированные операции являются единственными инструкциями процессора, которые могут гарантировать согласованность синхронизации. Это просто невозможно реализовать объект синхронизации без использования взаимосвязанных операций вообще.

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

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

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