2

I know Boost has support for mutexes and lock_guard, which can be used to implement critical sections.

But Windows has a special API for critical sections (see EnterCriticalSection and LeaveCriticalSection) which is a LOT faster than a mutex (for rarely contended, short sections of code).

Hence my question - it is possible in Boost to take advantage of this API, and fallback to spinlock/mutex/futex-based implementation on other platforms?

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • In helpful pointers: http://codereview.stackexchange.com/questions/1836/c-critical-section-with-timeout could be nice and this mailing list discussion http://comments.gmane.org/gmane.comp.lib.boost.devel/54585 (from 2001) – sehe Jan 10 '16 at 20:57
  • For trivia: Boost Smart Pointer, Boost Signals2, Boost Asio and Boost Container contain implementation details using CriticalSection (you can nick the wrappers from there, likely) – sehe Jan 10 '16 at 21:07
  • Not a duplicate IMO; this question is about Boost, the other question is about the standard library. – Harry Johnston Jan 10 '16 at 21:11
  • @HarryJohnston fixed – sehe Jan 10 '16 at 21:18

3 Answers3

2

The simple answer is no.

Here's some relevant background from an old mailing list thread:

BTW. I am agree that mutex is more universal solution from a

performance point of view. But to be fair - CS are faster in simple design. I believe that possibility to support them should be at least taken in account.

This was the article that someone pointed me to. The conclusion was that CS are only faster if:

  • There are less than 8 threads total in the process.
  • You weren't running in the background.
  • You weren't on an dual processor machine.

To me this means that simple testing yields good CS performance results, but any real world program is better off with a full blown mutex.

I'm not adverse to supporting a CS implementation. However, I originally chose not to for the following reasons:

  • You get either construction and destruction hits from using a PIMPL idiom or you must include Windows.h in the Boost.Threads headers, which I simply don't want to do. (This can be worked around by emulating a CS ala OPTEX from the MSDN.)
  • According to this research paper most programs won't benefit from a CS design.
  • It's trivial to code a (non-portable) critical_section class that follows the Mutex model if you truly can make use of this.

For now I think I've made the right choice, though down the road we may change the implementation to use a critical section or OPTEX.

Bill Kempf

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Mmm. Some scouring about does indicate that it likely uses Mutex, not CriticalSection. Let me look a bit further before I conclude – sehe Jan 10 '16 at 20:48
  • 1
    In Windows, a critical section is NOT a mutex. It's a user-mode object, whereas a mutex is a kernel-mode object. – rustyx Jan 10 '16 at 20:54
  • Apparently this implementation detail has changed over the history of Boost Thread. I can only assume for good reason. I suggest writing your own BasicLockable model - one might be floating around the webs – sehe Jan 10 '16 at 20:54
  • @rustyx I know this. I wanted to stress that even though `boost::mutex` is named "mutex", it doesn't necessarily imply that the implementation uses the Win32 primitive by the same name. (Although, all that is moot by now) – sehe Jan 10 '16 at 20:55
  • Updated my answer to be more relevant. – sehe Jan 10 '16 at 21:03
  • Doesn't invalidate your answer, but what the developer is saying there is very hard for me to believe. AFAIK, using a mutex always requires a transition to kernel mode, using a critical section doesn't. If you're dealing with a low-contention scenario (which is surely the most common case?) that must significantly favour the critical section. – Harry Johnston Jan 10 '16 at 21:22
  • Yeah. I was just quoting for relevance, not as proof. Like my original answer said YMMV, profile first (and if all else fails, steal a good wrapper :)) – sehe Jan 10 '16 at 22:11
2

Speaking as someone who helps out maintaining Boost.Thread, and as someone who failed to get an event object into Boost.Thread, I don't think critical sections have ever been added nor would be added to Boost for these reasons:

  1. A Win32 critical section is trivially easy to build using a boost::atomic and a boost::condition_variable, so much so it isn't really worth having an official one. Here is probably the most complex one you could imagine, but extremely configurable including being constexpr ready (don't ask!): https://github.com/ned14/boost.outcome/blob/master/include/boost/outcome/v1/spinlock.hpp#L331

    You can build your own simply by matching (Basic)Lockable concept and using atomic compare_exchange (non-x86/x64) or atomic exchange (x86/x64) and then grab it using a lock_guard around the critical section.

    Some may object that a win32 critical section is not this. I am afraid it is: it simply spins on an atomic for a spin count, and then lazily tries to allocate a win32 event object which it then waits upon. Nothing special.

  2. As much as you might think critical sections (really user mode mutexes) are better/faster/whatever, they probably are not as great as you might think. boost::mutex is a big vast heavyweight thing on Windows internally using a win32 semaphore as the kernel wait object because of the need to emulate thread cancellation and to behave well in a general purpose use context. It's easy to write a concurrency structure which is faster than another for some single use case, but it is very very hard to write a concurrency structure which is all of:

    1. Faster than a standard implementation in the uncontended case.
    2. Faster than a standard implementation in the lightly contended case.
    3. Faster than a standard implementation in the heavily contended case.

    Even if you manage all three of the above, that still isn't enough: you also need some guarantees on worst case progression ordering, so whether certain patterns of locks, waits and unlocks produce predictable outcomes. This is why threading facilities can appear to look slow in narrow use case scenarios, so Boost.Thread much as the STL can appear to be much slower than hand rolled locking code in say an uncontended use case.

  3. Boost.Thread already does substantial work in user mode to avoid going to kernel sleep on Windows. On POSIX any of the major pthreads implementations also does substantial work to avoid kernel sleeps and hence Boost.Thread doesn't replicate that work. In other words, critical sections don't gain you anything in terms of scaling to load behaviours, though inevitably Boost.Thread v4 especially on Windows does a ton load of work a naive implementation does not (the planned rewrite of Boost.Thread is vastly more efficient on Windows as it can assume Windows Vista or above).

Niall Douglas
  • 9,212
  • 2
  • 44
  • 54
  • 1
    Building your own is bad advice IMO. It's too easy to get wrong. The interesting part is the explicit decision to only have a single mutex type that tries to do all three scenarios, rather than allowing the programmer to choose a type specific to their use case. (And I can see arguments in favour of doing it that way, though I'm not sure it's the choice I'd have made.) – Harry Johnston Jan 11 '16 at 20:42
  • Well, according to [this](https://blogs.msdn.microsoft.com/oldnewthing/20140911-00/?p=44103) the critical section API has some optimizations to not spin forever etc. It would be nice to not have to reinvent all this. Although of course in a portable implementation one would have to reinvent it anyway. – rustyx Jan 12 '16 at 16:04
  • Furthermore, it spins only if you specify a non-zero spincount. By default it [never spins](https://web.archive.org/web/20150419055323/https://msdn.microsoft.com/en-us/magazine/cc164040.aspx), but does a wait as soon as a contention occurs. – rustyx Jan 13 '16 at 08:27
  • I love this answer for informativeness! (I think I'll bookmark it as a reference) – sehe Jan 29 '16 at 12:21
1

So, it looks like the default Boost mutex doesn't support it, but asio::detail::mutex does.

So I ended up using that:

#include <boost/asio/detail/mutex.hpp>
#include <boost/thread.hpp>

using boost::asio::detail::mutex;
using boost::lock_guard;

int myFunc()
{
  static mutex mtx;
  lock_guard<mutex> lock(mtx);
  . . .
}
rustyx
  • 80,671
  • 25
  • 200
  • 267