What exactly does `threading=multi` do when compiling boost?
Asked Answered
K

4

12

I am not entirely sure what exactly the threading=multi flag does when building boost. The documentation says:

 Causes the produced binaries to be thread-safe. This requires proper
 support in the source code itself.

which does not seem to be very specific. Does this mean that the accesses to, for example, boost containers are guarded by mutexes/locks or similar? As the performance of my code is critical, I would like to minimize any unnecessary mutexes etc.

Some more details:

My code is a plug-in DLL which gets loaded into a multi-threaded third-party application. I statically link boost into the DLL (the plug-in is not allowed to have any other dependencies except standard Windows DLLs, so I am forced to do this).

Although, the application is multi-threaded, most of the functions in my DLL are only ever called from a single thread and therefore the accesses to containers need not be guarded. I explicitly guard the the remaining places of my code, which can be called from multiple threads, by using boost::mutex and friends.

I've tried building boost with both threading=multi and threading=single and both seem to work but I'd really like to know what I am doing here.

Kendyl answered 31/12, 2013 at 15:51 Comment(9)
In order to find out what it means exactly, one has to 1) scan Boost.Build feature configuration files to see what macros threading=multi defines; 2) scan all the source tree to discover how these macros affect various parts of the code.Pianoforte
For example, see this answer.Pianoforte
Well I would think that there should be some code specification specifying what threading=multi should be used for in the boost code. Thanks for bringing my attention to that link. So this means that shared_ptr is guarded when threading=multi is set. What about other container classes like maps etc.?Kendyl
You should take a look at a particular library documentation, to see its thread-safety guaratees. Usually, they are similar to the STL ones: concurrent access to different containers is safe, concurrent read-only access to the same container is safe - with no regards to threading mode.Pianoforte
It turns out that I misled you regarding the relation between threading=multi and BOOST_HAS_THREADS. Please, see the following thread. The bottom line is that mt defines BOOST_HAS_PTHREADS only.Pianoforte
@IgorR. Would you please post that as an answer? You deserve it for actually digging out those threads.Idealize
This dates from the previous century, back when compilers still had CRT implementations that were single threaded. That's ancient history, those implementations are gone. As is any code that was once in Boost that still optimized for it.Guarantor
@Hans Passant well there are a lot of boost code optimized for single-threaded mode. But one should enable it explicitly, threading=single won't help.Pianoforte
Yeah, using threads=single will mostly not help due to all the autodetection of the threading mode (which these days will pick MT mode). It's too bad that the original intent of threading=single wasn't kept, along with the default behavior we have today for cases where neither threading=single|multi was specified (probably the 99% case).Liatris
L
19

No, threading=multi doesn't mean that things like boost containers will suddenly become safe for concurrent access by multiple threads (that would be prohibitively expensive from a performance point of view).

Rather, what it means in theory is that boost will be compiled to be thread aware. Basically this means that boost methods and classes will behave in a reasonable default way when accessed from multiple threads, much like classes in the std library. This means that you cannot access the same object from multiple threads, unless otherwise documented, but you can access different objects from multiple threads, safely. This might seem obvious, even without explicit support, but any static state used by the library would break that guarantee if not protected. Using threading=multi guarantees that any such shared state is property guarded by mutex or some other mechanism.

In the past similar arguments or stdlib flavors were available for the C and C++ std libraries supplied my compilers, although today mostly just the multithreaded versions are available.

There is likely little downside to compiling with threading=multi, given that only a limited amount of static state need to be synchronized. Your comment that your library will mostly only be called by a single thread doesn't inspire a lot of confidence - after all, those are the kinds of latent bugs that will cause you to be woken up at 3 AM by your boss after a night of long drinking.

The example of boost's shared_ptr is informative. With threading=single, it is not even guaranteed that independent manipulation of two shared_ptr instances, from multiple threads, is safe. If they happen to point to the same object (or, in theory, under some exotic implementations even if they don't), you will gen undefined behavior, because shared state won't be manipulated with the proper protections.

With threading=multi, this won't happen. However, it is still not safe to access the same shared_ptr instance from multiple threads. That is, it doesn't give any thread safety guarantees which aren't documented for the object in question - but it does give the "expected/reasonable/default" guarantees of independent objects being independent. There isn't a good name for this default level of thread-safety, but it is in fact what's generally offered all standard libraries for multi-threaded languages today.

As a final point, it's worth noting that Boost.Thread is implicitly always compiled with threading=multi - since using boost's multithreaded classes is an implicit hint that multiple threads are present. Using Boost.Thread without multithreaded support would be nonsensical.

Now, all that said, the above is the theoretical idea behind compiling boost "with thread support" or "without thread support" which is the purpose of the threading= flag. In practice, since this flag was introduced, multithreading has become the default, and single threading the exception. Indeed, many compilers and linkers which defaulted to single threaded behavior, now default to multithreaded - or at least require only a single "hint" (e.g., the presence of -pthread on the command line) to flip to multithreaded.

Apart from that, there has also been a concerted effort to make the boost build be "smart" - in that it should flip to multithreaded mode when the environment favors it. That's pretty vague, but necessarily so. It gets as complicated as weak-linking the pthreads symbols, so that the decision to use MT or ST code is actually deferred to runtime - if pthreads is available at execution time, those symbols will be used, otherwise weakly linked stubs - that do nothing at all - will be used.

The bottom line is that threading=multi is correct, and harmless, for your scenario, and especially if you are producing a binary you'll distribute to other hosts. If you don't special that, it is highly likely that it will work anyway, due to the build-time and even runtime heuristics, but you do run the chance of silently using empty stub methods, or otherwise using MT-unsafe code. There is little downside to using the right option - but some of the gory details can also be found in the comments to this point, and Igor's reply as well.

Liatris answered 8/1, 2014 at 9:25 Comment(11)
Thank you for this great answer. Can you also comment on Igor's answer? Is it true that what you say above is actually true for when _MT is defined and that threading=multi actually only has little effect?Kendyl
@Kendyl I suggest you to not trust anyone, but just test this by yourself, like I've just done :). Make a dummy test & jam files, like the ones described by Joaquin in the aforementioned link, and see what macros are defined for threading=single. Besides, see boost/config/suffix.hpp.Pianoforte
Indeed, the way I described it above is the way the option was intended, long ago, when it was introduced. In the intervening time, a lot of things have changed, with the "default" progressively becoming MT rather than ST, and eventually many environments assuming MT always (see my note on standard library behaviour above). The upshot is that the boost compilation process tries to be smart - in the same way that Boost.Thread always has thread support, regardless of the compile options, Boost may actually compile in thread support if it detects certain preporcesssor macros defined.Liatris
For example, if _REENTRANT is present, it compile in threaded support, even if threaded=single is defined - since that will make things "just work" in a large number of cases (in fact, probably most cases since MT is the default these days) - but then there is the complication of how to implement thread support, which is where the BOOST_HAS_PTHREADS mess comes in. Basically if BOOST_HAS_THREADS is defined, but BOOST_HAS_PTHREADS is not, you may or may not get "do nothing" stub methods for synchronization, depending vagaries such as the link line, platform, compiler, etc.Liatris
This thread from more than five years ago, probably gives a deeper and more authoritative insight than I can give, these days - but the bottom line is that is all heuristic, and while threaded=single may work (because boost smartly overrides that based on what it sees in the environment), threaded=multi is what you want, as long as you are targeting environments where pthreads is available, since it's guaranteed to work and not leave you with "do nothing" stub methods.Liatris
OK I just did a check: on linux threading=multi does not have any effect. When compiling for mingw32 from linux (which is what I do on our build bot) threading=single defines BOOST_DISABLE_THREADS.Kendyl
Thanks for the link. This is my fist bounty question and I am a bit of a stackoverflow newbie. I think BeeOnRope's answer was more informative to me, so I'm inclined to give him the bounty. However, what Igor wrote also seems important in this context. @BeeOnRope: Could you edit your answer to include a short sentence about threading=multi not having an effect on many platforms? Then I will award you the bounty. I'll upvote Igor's answer too. Or is there a better way to sort of award both of you?Kendyl
I'm editing my answer with a summary of the above discussion. You can't split your existing bounty, but you can can award Igor a separate bounty - see this question for details (the second answer has the info on multiple bounties). In any case, your bounty is small and this isn't really about the bounty anyway. I've upvoted Igor's correct and researched answer, and comments which should be worth about 30 pts anyway.Liatris
@Kendyl regarding mingw and BOOST_DISABLE_THREADS - check that it's undefined with thread=multi. Note that with some (buggy) compilers BOOST_DISABLE_THREADS might be defined even with threading=multi.Pianoforte
@Igor threading=single doesn't even work on the newest boost version. I get an error when configuring with b2. It works on boost 1.46 which in deed does enable BOOST_DISABLE_THREADS. With threading=multi it disables BOOST_DISABLE_THREADS (this is with gcc mingw32). @BeeOnRope: I need to wait another 2 hours before I can reward you the bountyKendyl
Yup, we are in such an MT world, that the old options are really dying out - to the point where they apparently don't even compile anymore.Liatris
P
6

After some digging, it turns out that threading=single doesn't have much effect, as one would expect. In particular, it does not affect BOOST_HAS_THREADS macro and thus doesn't configure libraries to assume single threaded environment.

With gcc threading=multi just implies #define BOOST_HAS_PTHREADS, while with MSVC it doesn't produce any visible effect. Paricularly, _MT is defined both in threading=single and threading=multi modes.

Note however, that one can explicitly configure Boost libraries for single-threaded mode by defining the appropriate macro, like BOOST_SP_DISABLE_THREADS , BOOST_ASIO_DISABLE_THREADS, or globally with BOOST_DISABLE_THREADS.

Pianoforte answered 8/1, 2014 at 9:34 Comment(7)
Thanks for checking this. How does this relate to BeeOnRope's answer? Does this mean that _MT actually produces a thread-aware boost and that threading=multi doesn't really have any effect on MSVC?Kendyl
@Kendyl _MT implies BOOST_HAS_THREADS, as you can see in boost/config/suffix.hpp or with a dummy test, as I suggested in the other comment. So - yes, the libraries are configured for multithreaded environment, despite threading=single.Pianoforte
The problem is that BOOST_HAS_THREADS defined, but BOOST_HAS_PTHREADS undefined maybe result in totally fine MT code (for platforms which don't support pthreads), or a sub-optimal code path (for platforms which support pthreads as the preferred way for writing MT code, but which have a fallback - I include this to be complete as I'm not aware of any such platforms today, but it's been a long time since I worked on boost), or generating stub methods for all MT functions. The latter is what you really want to avoid.Liatris
Additionally, the heuristics used by the jam builds are often based on the "current environment" - e.g., whether the current compiler was build with pthreads support, or whether the current compiler defines MT or _REENTRANT or friends, which makes a lot of sense when running your application locally since it will "just work" - but if you are statically embedding this library into your component for redistribution, the heuristics based on your localhost are pretty much meaningless. So you should use threading=multi. I agree, that it is very likely it makes no difference, but it is correct.Liatris
@Liatris with msvc there's BOOST_HAS_WINTHREADS instead, and it's defined with no regards to threading feature. In general, if BOOST_HAS_THREADS is defined, but a library can't select a particular threading library (eg, BOOST_HAS_PTHREADS is undefined on generic posix) - the things just won't compile (see boost/smart_ptr/detail/spinlock.hpp for example).Pianoforte
Right, the way it works on MSVC is that there are no longer any non-MT runtime libraries, so BOOST_HAS_THREADS is always defined. So even if you specify threading=single you get MT behaviors. As below, this kind of things is progressively true on all platform, but the intent and defined behavior for threading=... is to set the threading support. It so happens that if you are compiling in an environment where the stdlib/compiler has thread support BOOST_HAS_THREADS will be defined, regardless of the threading flag, but it still may not work correctly (e.g., -pthreads may not be linked).Liatris
BTW, all the constants like MT and _REENTRANT and so on aren't the business of boost to define at all, so the value of threading won't affect that.Liatris
L
2

Let's be concise. Causes the produced binaries to be thread-safe means that Boost code is adapted to that different threads can use different Boost objects. That means in particular that the Boost code will have a special care of the thread-safety of accesses to hidden global or static objects that might be use by the implementation of Boost libraries. It does not automatically allow different threads to use the same Boost objects at the same time without protection (locks/mutexes/...).

Edit: Some Boost libraries may document an extra thread-safety for specific functions or classes. For example asio::io_service as suggested by Igor R. in a comment.

Lisabeth answered 8/1, 2014 at 12:2 Comment(2)
This too broad (and not universally correct) statement. In the reality each Boost library treats "thread safety" in its own way, and even within the same library every object might have its own specific guarantees with regards to thread-safety (eg. asio::ip:tcp::socket is not threadsafe, while asio::io_service is).Pianoforte
My wording was wrong. When I said "It does not allow different threads to use the same Boost objects at the same time without protection", I should have said that some Boost libraries may assure thread-safety for concurrent accesses of shared objects with specific classes/functions.Lisabeth
C
0

The documentation says everything that is needed: this option ensures thread safety. That is, when programming in a multithreaded environment, you need to ensure certain properties like avoiding unrestricted access to e.g. variables.

I think, enabling this option is the way to go.

For further reference: BOOST libraries in multithreading-aware mode

Carder answered 31/12, 2013 at 15:55 Comment(5)
When you say it "ensures thread-safety" this can mean a lot of things: for example, that all accesses to shared variables are guarded. But that's exactly what I don't want as my code is performance critical and I know that some portions of my code will only ever be called from a single thread.Kendyl
Also the reference that you mention does not really address my question: they talk about the suffix of the library name. As I am linking statically this really isn't an issue anyway.Kendyl
Well, in that mentioned question there are some explanations, but yes none that really clarifies the differences. But anyway, if a thread-safe boost library harms the performance of your code, then you should use the one with single-thread support. I think in the end, the best way is to measure it.Carder
-1 as "this option ensures thread safety" is just patently wrong and misguiding. I don't care that the docs state this. That's in a context. In practice, many boost libraries require explicit configuration in order to allow thread-safety. And no, thread safety does not "magically" happen all around. This is dangerous misinformation if stated without disclaimer.Idealize
@Idealize then they should change the documentation. And I never stated any sort of magic.Carder

© 2022 - 2024 — McMap. All rights reserved.