Критическая секция Windows — как полностью отключить вращение

Я пытаюсь установить счетчик спинов для CRITICAL_SECTION на ноль разными способами:

int main()
{
    CRITICAL_SECTION cs;

    ::InitializeCriticalSection(&cs);
    printf("Spin count by default %08X\n", cs.SpinCount);
    ::DeleteCriticalSection(&cs);

    ::InitializeCriticalSectionAndSpinCount(&cs, 0);
    printf("Spin count with zero spin count init %08X\n", cs.SpinCount );
    ::DeleteCriticalSection(&cs);


    ::InitializeCriticalSectionEx(&cs, 0, 0);
    printf("Spin count with zero spin count and flags init %08X\n", cs.SpinCount );
    ::DeleteCriticalSection(&cs);

    ::InitializeCriticalSection(&cs);
    ::SetCriticalSectionSpinCount(&cs, 0);
    printf("Spin count after explicit reset to zero %08X\n", cs.SpinCount);
    ::DeleteCriticalSection(&cs);
}

В Windows 7 все результаты равны 0, как и ожидалось.

В Windows 10 все, кроме последнего, приводят к значению 0x020007D0. Последний результат 0x02000000.

Судя по всему, 0x07D0 — это фактическое количество вращений (2000 в десятичной системе), а 0x02000000 — один из следующих флагов:

#define RTL_CRITICAL_SECTION_FLAG_NO_DEBUG_INFO         0x01000000
#define RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN          0x02000000
#define RTL_CRITICAL_SECTION_FLAG_STATIC_INIT           0x04000000
#define RTL_CRITICAL_SECTION_FLAG_RESOURCE_TYPE         0x08000000
#define RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO      0x10000000

Я боюсь, что RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN может привести к вращению критической секции, даже если я попрошу ее не вращаться с помощью SetCriticalSectionSpinCount.

Есть ли способ не определять RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN с помощью стандартных документированных API?


person Alex Guteniev    schedule 19.02.2019    source источник
comment
Похоже, что API может установить минимальное количество вращений. Почему ты не хочешь, чтобы он крутился? Спиннинг это хорошо.   -  person Jonathan Potter    schedule 19.02.2019
comment
Я знаю, что конкретная операция выполняется медленно, поэтому, если критический раздел находится в собственности, нет смысла сжигать вращающиеся ресурсы, он вряд ли скоро освободится, скорее было бы полезно уступить другому потоку, чтобы ресурс, вероятно, был бесплатно раньше.   -  person Alex Guteniev    schedule 19.02.2019
comment
Это не кажется полностью соблюдаемым минимумом, так как установка количества вращений на 1 действительно устанавливает его на 1. И вызов SetCriticalSectionSpinCount также работает - он может установить количество вращений на ноль. Единственная проблема, которая у меня есть, это явно принудительный флаг RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN.   -  person Alex Guteniev    schedule 19.02.2019
comment
Всегда есть TryEnterCriticalSection(), если вы не хотите блокировать. Или вы можете использовать мьютекс. Обратите внимание, что RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN не задокументировано; похоже, нет никакой информации о том, для чего это на самом деле.   -  person Jonathan Potter    schedule 20.02.2019
comment
TryEnterCriticalSection не вариант, так как я хочу заблокировать, если объект принадлежит. Мьютекс WinAPI также не вариант, так как я не хочу переходить в ядро, когда объект теперь принадлежит. Ближайшей альтернативой является Slim Reader/Writer Lock, когда он заблокирован AcquireSRWLockExclusive/ReleaseSRWLockExclusive, он, похоже, делает именно то, что мне нужно, за исключением того, что он не поддерживает рекурсию. Реализация std::mutex из C++, boost::mutex или Visual Studio также ведет себя так, как я хочу (и есть даже их рекурсивные версии). Тем не менее, мой вопрос касается контроля над поведением критической секции.   -  person Alex Guteniev    schedule 20.02.2019


Ответы (1)


Заглянув в реализацию, я сам нашел ответ.

Когда InitializeCriticalSectionEx используется с ненулевым счетчиком спинов, флаг RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN не устанавливается. Итак, этот код выводит 00000001:

::InitializeCriticalSectionEx(&cs, 1, 0);
printf("Spin count after explicit one %08X\n", cs.SpinCount);
::DeleteCriticalSection(&cs);

Одно вращение почти равно нулю. И, кроме того, при вызове SetCriticalSectionSpinCount впоследствии его можно обнулить. Итак, этот код выводит 00000000:

::InitializeCriticalSectionEx(&cs, 1, 0);
::SetCriticalSectionSpinCount(&cs, 0);
printf("Spin count after explicit one and then reset to zero %08X\n", cs.SpinCount);
::DeleteCriticalSection(&cs);

Конечно, должна быть веская причина, чтобы отключить вращение. По умолчанию, как указал @JonathanPotter, вращение — это хорошо. В противном случае это не было бы установлено как поведение по умолчанию. Поэтому я даже не применил решение по отключению вращения к своей исходной проблеме.

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

person Alex Guteniev    schedule 23.02.2019