Проблема многопоточности C ++ - единственный выход - мьютекс?

У меня есть переменная счетчика, к которой будут обращаться несколько потоков, которые будут увеличивать / уменьшать ее. Он не должен обновляться несколькими потоками одновременно.

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

Есть ли еще что-нибудь, что я могу сделать без мьютекса? Использование мьютекса снижает производительность (см. http://www.codeguru.com/forum/showthread.php?t=333192). Я считаю, что в Java есть ключевое слово, которое вы можете использовать в объявлении переменной для достижения этой цели (называется ли это «синхронизированный»?), Но есть ли вообще такое в C ++?

Я знаю, что volatile - не то ключевое слово, которое я ищу.

Большое Вам спасибо.


person Andy    schedule 28.01.2010    source источник
comment
Критическая секция по-прежнему будет работать здесь, если к счетчику обращается только один процесс. Он может использоваться несколькими функциями в рамках одного и того же процесса. Фактически, критические секции работают быстрее, чем мьютексы.   -  person John Dibling    schedule 28.01.2010
comment
re Java: нет, synchronized в java эквивалентен критическому разделу, который отличается от volatile и атомарного увеличения / уменьшения.   -  person finnw    schedule 28.01.2010


Ответы (8)


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

Вы можете получить доступ к этим атомарным инструкциям в своем собственном коде. Windows предоставляет функцию InterlockedIncrement() и glib предоставляет эквиваленты. В ассемблере x86 вы можете напрямую использовать LOCK CMPXCHG и kin.

C ++ ничего не знает об этих концепциях - вы должны использовать их сами; в C ++ нет волшебных ключевых слов для обеспечения безопасности потоков.

См. атомарную инструкцию

person Will    schedule 28.01.2010

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

void IncDec( bool inc ) {
   EnterCritical Section( theCS );
   if ( inc ) {
     theVar++;
   }
   else {
     thevar--;
   }
   LeaveCriticalSection( theCS );
}

и вызовите его из других ваших функций.

person Community    schedule 28.01.2010
comment
В библиотеке qt есть хорошая реализация атомарных операций doc.trolltech.com/4.3/atomic- operations.html - person Jay; 28.01.2010

Судя по описанию, возможно, вам понадобятся только InterlockedIncrement и ассоциированная функция декремента.

Edit - это функции Windows ... Я не останавливался, чтобы спросить, на какой платформе.

person Mark Wilkins    schedule 28.01.2010
comment
Значит, нет ничего, что можно было бы поставить перед объявлением переменной, в котором указывается, что доступ к ней должен быть атомарным? Я знаю, что const, mutable и volatile - это слова-квалификаторы C ++, но они не подходят. - person Andy; 28.01.2010
comment
@ Энди Нет, нет. Текущий стандарт C ++ вообще не рассматривает многопоточность. - person ; 28.01.2010

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

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

person nos    schedule 28.01.2010

В Win32 IntelockedIncrement / IntelockedIncrement64 и связанные операции компилируются в инструкции x86, которые позволяют выполнять атомарные операции на уровне процессора с 32- или 64-битными словами (в зависимости от вашей архитектуры). Это отлично работает в случае простого счетчика, но, естественно, не сработает, если вы пытаетесь синхронизировать большую структуру с несколькими словами.

PS из здесь, соответствующий asm, который вам нужно будет реализовать это в системе, отличной от Win32, работающей на x86.

inline long InterlockedExchangeAdd( long* Addend, long Increment )
{
long ret;
__asm__ (
/* lock for SMP systems */
"lock\n\t"
"xaddl %0,(%1)"
:"=r" (ret)
:"r" (Addend), "0" (Increment)
:"memory" );
return ret;
}

inline long InterlockedIncrement( long* Addend )
{
return InterlockedExchangeAdd( Addend, 1 );
}

inline long InterlockedDecrement( long* Addend )
{
return InterlockedExchangeAdd( Addend, -1 );
}
person Doug T.    schedule 28.01.2010

Вы можете использовать атомарный тип для переменной счетчика - например, sig_atomic_t (в GNU libc). Тогда нет необходимости в синхронизации, поскольку состояние гонки не может произойти, операция над этой переменной гарантированно будет атомарной.

person Zoltán Szőcs    schedule 28.01.2010
comment
sig_atomic_t имеет атомарные записи (т.е. вам не нужно беспокоиться только о 3 байтах переменной, обновляемых перед переключением контекста), но не атомарное увеличение / уменьшение. См. Этот вопрос: stackoverflow.com/questions/1762148/atomic-instruction - person finnw; 28.01.2010

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

person rjnilsson    schedule 28.01.2010
comment
Спасибо. В C ++ есть что-то, что делает переменную локальной для потока - declspec (thread), но, вероятно, это специфично для платформы Windows. - person Andy; 28.01.2010
comment
@Andy: __declspec (thread) делает переменные привязанными к потоку - это означает, что они не могут быть разделены между потоками (ну, по крайней мере, не нарушая их цели). - person rjnilsson; 28.01.2010
comment
@Andy: О, и __declspec (thread) не зависит от Windows, а от VC ++. - person rjnilsson; 28.01.2010

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

Как вы узнаете, что нашли все места, к которым переменная может быть доступна, если вы не создадите единственную функцию, используя критическую секцию для ее изменения?

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

person jmucchiello    schedule 28.01.2010