0

On Windows. I think a critical-section object is better than a mutex for synchronization between threads within one process. This is because the CS object works in user mode, whereas the mutex object works in kernel mode. So CS objects are lighter than mutex objects.

But I've seen a case where a process uses a mutex instead of a CS. The mutex didn't even use a timeout.

So, I'm really curious, in what cases are a mutex better than a CS within a process?

selbie
  • 100,020
  • 15
  • 103
  • 173
Adam
  • 51
  • 5
  • CS is OS-specific – so if you want to write *portable* code, you shouldn't use it. – Aconcagua Mar 08 '21 at 06:55
  • 1
    @Aconcagua more that if you do use it you should use it in a way that's easy to replace it with something else on other platforms – Alan Birtles Mar 08 '21 at 07:00
  • You should check to see if on MS-Windows `std::mutex` is implemented using critical-section. There is no need for `std::mutex` it be implemented with a heavy-weight MS-Windows cross-process mutex as Standard C++ has no concept of cross-process synchronization. – Richard Critten Mar 08 '21 at 09:00
  • removing C++ tag. – selbie Mar 08 '21 at 11:14
  • CS is useful for its light-weight and support for spinning. Mutex is useful for interprocess sync, detecting deadlock due to unexpected thread/process termination or bugs, supporting APCs and wait timeouts. It is not unusual that the difference isn't observable at all, waits tend to be long. std::mutex in MSVC is built on top of the concurrency runtime, it uses neither CS nor mutex. – Hans Passant Mar 08 '21 at 15:22

2 Answers2

0

For the most part you are correct - Critical Section objects are preferred over Win32 Mutex objects for all the reasons you listed. std::mutex from the C++ library is just another variation of critical sections as well. Same for pthread_mutex_t instances.

The only case I can think of for using a Win32 mutex between thread of the same process instead of a simpler critical section is with a "named" mutex. With CRITICAL_SECTIONs, both threads need to share a pointer to the CRITICAL_SECTION.

But let's say you have two disparate pieces of code that are in different components and threads. Because of the way the code is presently architected (e.g. separate DLLs and libraries), there's really no easy way to share a CRITICAL_SECTION pointer between the two code paths without doing a lot of plumbing between the two.

A named mutex solves that. The name is a string you pass as the 3rd parameter to CreateMutex. Both components can do something like the following when they initialize

HANDLE hMutex = CreateMutex(nullptr, FALSE, L"App_Resource_Thing");

Now they have unique handles to the SAME mutex object. Then they can both enter the mutex with their handle:

DWORD dwWait = WaitForSingleObject(hMutex, INFINITE);
if ((dwWait == 0) || (dwWait == WAIT_ABANDONED))
{
    // operate on shared resource

    // release mutex to whoever wants it next
    ReleaseMutex(hMutex);
}

Now with named mutexes comes another issue - security. With the sample above, another application, assuming it knows the name of the mutex, can come along and lock it forever That will effectively freeze the other app. Or more generically, just another instance of your own EXE running would conflict with the first instance if using a hardcoded name. Using the SECURITY_DESCRIPTOR and/or generating a unique name for the mutex at runtime that can't be guessed by other rogue processes would help mitigate that issue.

selbie
  • 100,020
  • 15
  • 103
  • 173
  • Also I don't think `WaitForMultipleObjects` can be used with critical sections? For example it is very common that your thread only waits for two things: wait for a mutex and waits for an event signalling to close. – Lundin Mar 08 '21 at 11:45
  • Correct. But the OP had already called out the `The mutex didn't even use a timeout.` in his cited example. It's also somewhat a design flaw, if not an odd design pattern, to rely on a timeout for entering a critical section. So I didn't really want to encourage that by mentioning it. – selbie Mar 08 '21 at 11:47
0

I assume the question is about Windows Mutex, not std::mutex.

  • Mutex can use timeout with WaitForSignleObject or other waiting function, if it specifies a timeout.
  • [Msg]WaitForMultipleObjects[Ex] can wait for mutex or another object (another mutex). With critical section you can only wait on critical section, this wait cannot be interrupted by something else.
  • Mutex can signal when thread exits while owning a mutex, see WAIT_ABANDONED_0
  • Mutex is fair, critical section is unfair. This means that if you have constant contention of two or more threads, with critical section one is likely to "win" constantly, whereas with mutex threads would (approximately) follow FIFO order.

Note that first two points can be addressed with using CONDITION_VARIABLE in conjunction with CRITICAL_SECTION, but it takes more code to write.


Note that there's a superior to CRITICAL_SECTION synchronization object called SRWLOCK. It can maintain shared ownership, but you can use solely exclusive ownership API. It is somewhat more lightweight than critical section, since it does not have to maintain recursion count and some legacy compatibility stuff.

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79