Windows Critical Section - how to disable spinning completely
Asked Answered
G

1

3

I'm trying to set spin count for CRITICAL_SECTION to zero by different methods:

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);
}

In Windows 7, all results are 0 as expected.

In Windows 10, except the last one, all result in 0x020007D0 value. The last one result in 0x02000000.

Apparently, 0x07D0 is actual spin count (2000 in decimal), and 0x02000000 is one of these flags:

#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

I'm afraid that RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN may cause critical section to spin even if I asked it not to spin by using SetCriticalSectionSpinCount.

Is there any way not to define RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN by using standard documented APIs ?

Gomuti answered 19/2, 2019 at 11:29 Comment(5)
It sounds like the API might enforce a minimum spin count. Why do you not want it to spin? Spinning is good.Hallucinatory
I know the particular operation is slow, so if critical section is owned, there's no point in burning resources spinning, it is not likely to be free soon, rather it would be helpful to yield to another thread so that the resource is likely to be free sooner.Gomuti
It doesn't seem to be fully enforced minimum, as setting spin count to 1 really sets it to 1. And calling SetCriticalSectionSpinCount also works - it can set spin count to zero. The only problem I have is apparently enforced RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN flag.Gomuti
There's always TryEnterCriticalSection() if you don't want to block. Or you could use a mutex. Note that RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN is undocumented; there doesn't seem to be any information about what it's actually for.Hallucinatory
TryEnterCriticalSection is not an option, as I want to block if object is owned. WinAPI mutex is not an option either, as I don't want to go kernel when object is now owned. Closest alternative is Slim Reader/Writer Lock, when locked by AcquireSRWLockExclusive/ReleaseSRWLockExclusive, it seem to do exactly what I need, except that it is without recursion support. From C++, boost::mutex or Visual Studio implementation of std::mutex also behave as I want (and there are even recursive versions of those). Yet, my question is about taking control over Critial Section behavior.Gomuti
G
1

After peeking into the implementation, figured out answer myself.

When InitializeCriticalSectionEx is used with nonzero spin count, RTL_CRITICAL_SECTION_FLAG_DYNAMIC_SPIN flag is not set. So this code outputs 00000001:

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

One spin count is almost zero spin count. And, moreover, with calling SetCriticalSectionSpinCount afterwards it can be reset to zero. So this code outputs 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);

Sure, there should be compelling reason to disable spinning. By default, as @JonathanPotter pointed out, spinning is good. Otherwise it wouldn't have been set as default behavior. So I didn't even apply the solution for disabling spinning to my original problem.

On the other hand, it may be not the intention of maintainers of critical section to disrespect zero spin count passed to InitializeCriticalSectionEx or InitializeCriticalSectionAndSpinCount. They just made sure that plain InitializeCriticalSection gets automatic spin count.

Gomuti answered 23/2, 2019 at 10:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.