Ранее я написал очень простой многопоточный код, и я всегда знал, что в любой момент может произойти переключение контекста прямо в середине того, что я делаю, поэтому я всегда защищал доступ к разделяемые переменные через класс CCriticalSection, который входит в критический раздел при создании и оставляет его при уничтожении. Я знаю, что это довольно агрессивно, и я вхожу и покидаю критические разделы довольно часто, а иногда и вопиюще (например, в начале функции, когда я могу поместить CCriticalSection в более узкий блок кода), но мой код не падает, и он работает достаточно быстро .
На работе мой многопоточный код должен быть более плотным, требуя только блокировки / синхронизации на самом низком уровне.
На работе я пытался отладить какой-то многопоточный код, и я столкнулся с этим:
EnterCriticalSection(&m_Crit4);
m_bSomeVariable = true;
LeaveCriticalSection(&m_Crit4);
Теперь m_bSomeVariable
- это Win32 BOOL (не энергозависимый), который, насколько я знаю, определен как int, а при чтении и записи в x86 эти значения являются одной инструкцией, и так как переключение контекста происходит на границы команд, то нет необходимости синхронизировать эту операцию с критическим разделом.
Я провел еще несколько онлайн-исследований, чтобы выяснить, не нуждается ли эта операция в синхронизации, и я предложил два сценария, которые она сделала:
- ЦП реализует не по порядку исполнение или второй поток работает на другом ядре, а обновленное значение не записывается в ОЗУ, чтобы другое ядро могло видеть; и
- int не выровнен по 4 байта.
Я считаю, что номер 1 можно решить с помощью ключевого слова "volatile". В VS2005 и более поздних версиях компилятор C ++ окружает доступ к этой переменной с помощью барьеров памяти, гарантируя, что переменная всегда полностью записывается / читается в основную системную память перед ее использованием.
Номер 2 Я не могу проверить, я не знаю, почему выравнивание байтов будет иметь значение. Я не знаю набор инструкций x86, но нужно ли mov
дать 4-байтовый выровненный адрес? Если нет, вам нужно использовать комбинацию инструкций? Это привело бы к проблеме.
Итак ...
ВОПРОС 1: Использует ли ключевое слово «volatile» (простота, используя барьеры памяти и подсказку компилятору не оптимизировать этот код), освобождает программиста от необходимости синхронизировать 4-байтовую / 8- байт в переменной x86 / x64 между операциями чтения / записи?
ВОПРОС 2: Существует ли явное требование, чтобы переменная была выровнена по 4 или 8 байтов?
Я еще немного покопался в нашем коде и переменных, определенных в классе:
class CExample
{
private:
CRITICAL_SECTION m_Crit1; // Protects variable a
CRITICAL_SECTION m_Crit2; // Protects variable b
CRITICAL_SECTION m_Crit3; // Protects variable c
CRITICAL_SECTION m_Crit4; // Protects variable d
// ...
};
Теперь мне это кажется чрезмерным. Я думал, что критические секции синхронизируют потоки между процессами, поэтому, если у вас есть такой, вы можете его ввести, и никакой другой поток в этом процессе не может выполняться. Нет необходимости в критическом разделе для каждой переменной, которую вы хотите защитить, если вы находитесь в критическом разделе, то ничто другое не может вас прервать.
Я думаю, единственное, что может изменить переменные вне критической секции, - это если процесс разделяет страницу памяти с другим процессом (можете ли вы это сделать?), И другой процесс начинает изменять значения. Здесь также могут помочь мьютексы: именованные мьютексы являются общими для процессов или только для процессов с одинаковыми именами?
ВОПРОС 3: Является ли мой анализ критических секций правильным, и следует ли переписать этот код для использования мьютексов? Я посмотрел на другие объекты синхронизации (семафоры и спин-блокировки), они лучше подходят здесь?
ВОПРОС 4: Где наиболее подходят критические секции / мьютексы / семафоры / спин-блокировки? То есть к какой проблеме синхронизации они должны быть применены. Существуют ли значительные потери производительности при выборе одного над другим?
И пока мы на нем, я читал, что спин-блокировки не следует использовать в одноядерной многопоточной среде, только в многоядерной многопоточной среде. Итак, ВОПРОС 5: Это неправильно, или если нет, то почему это правильно?
Заранее спасибо за любые ответы:)