Community
cancel
Showing results for 
Search instead for 
Did you mean: 
mgmt1969
Beginner
1,349 Views

Are static mutexes local to a function safe?

Hello,

If I have a function with a static mutex:

void func()
{
if (some condition)
{
static tbb:spin_mutex mutex;
tbb::spin_mutex::scoped_lock lock(mutex);
// the rest of the if clause should now be protected
}
}

is the initialisation of the static mutex above thread-safe?
Is it also consistent, ie. would a thread be able to enter the clause while another thread is still busy initialising the mutex for the first time?

Thank you,
manuel

0 Kudos
12 Replies
RafSchietekat
Black Belt
1,349 Views

I don't see the distinction between thread-safe and consistent, but I'd say yes to both.
Carey
Beginner
1,349 Views

In the case of parallel programs, statics are just plain nasty, try to avoid them.

If this is something you think you need then you may need to look at your design again and alter it so this wouldn't be required.

I'd have thought that a non static, but public mutex in a class that is used by methods in that class would be more suitable.



RafSchietekat
Black Belt
1,349 Views

Unmotivated big absolute statements are just plain nasty, try to avoid them.

(Added) Or in other words: would you care to elaborate?

jimdempseyatthecove
Black Belt
1,349 Views

The initialization of the static object should occur at

a) compile time when the object is POD
b) at program load time when the static class ctor's are called

IOW initialization of the static objects are complete prior to the call to main(), and thus prior to the instantiation of the thread pool.

Note, your func would protect the code in func, but not necessarilyprotect an arbitrary object referenced by the protected code in func.

You have to determine if it is necessary to protect the code, the object, or both. Then place the mutex accordingly.

For example, should you have

void YourLinkedList::InsertIntoList(Node* node);
void YourLinkedList::DeleteFromList(Node* node);

And should each function have a private (static)mutex then...

While you could not have multiple concurrent insert's OR multiple concurrent delete's
You could have a concurrent insert and delete (which could break your list).

Look carefully at your mutex requirements to assure what you are protecting is what you need protecting.

Jim Dempsey
RafSchietekat
Black Belt
1,349 Views

A correction is in order...Things are different for local static objects (6.7 Declaration statement [stmt.dcl]) than for non-local objects (3.6.2 Initialization of non-local objects [basic.start.init]): these objects are not (seen to be) initialised before main(). In a multithreaded environment it's a matter of what the ABI provides (until C++0x becomes effective), and what it does islock a mutexrelated to the initialisation of the object, because this initialisation can indeed occur after the thread pool has been created.

(I did not want to assume anything about the propriety or not of a function-local mutex without specific knowledge about its purpose, but of course I agree with Jim's predicated recommendations in that regard.)
ARCH_R_Intel
Employee
1,349 Views

I concur with Jim's and Raf's comments.In the rarecase that afunction-local mutex is approprate, and you do not have C++0x guarantees on initialization of thread-local objects, there is a TBB hack that should work.Be warned that it relies onthe undocumenteddetail that the internal implementation of spin_mutex's constructorzero-initializes it. Though this detail could change in the future, I'd be amazed if it did.

Here is thecode:
[cpp]#include "tbb/spin_mutex.h"
#include "tbb/aligned_space.h"

void func() {
    if( some_condition ) {
        static tbb::aligned_space<:SPIN_MUTEX> mutex;
        tbb::spin_mutex::scoped_lock( *mutex.begin() );
        // the rest of the if clause is now protected
    }
}
[/cpp]

The aligned_space object will be zero-initialized at compile time, thus avoiding a race to initialize it. This trick also works with the current implementation of spin_rw_mutex.cpp. Indeed we use the hack in src/tbb/observer_proxy.cpp.

Ordinarily, I do not like to use hacks that depend on undocumented features. However, the stated problem is a tough nut to crack without thread-safe initialization of function-local static variables.

A mutex that is safe to use in these circumstances and uses only documented TBB features is possible to build. Use a tbb::atomic<:BOOL> and compare_and_swap. To reduce busy-waiting contention, a thread should execute std::this_thread::yield() [defined in "/tbb/compat/thread"] after each failed compare_and_swap.
ARCH_R_Intel
Employee
1,349 Views

I concur with Jim's and Raf's comments.In the rarecase that afunction-local mutex is approprate, and you do not have C++0x guarantees on initialization of thread-local objects, there is a TBB hack that should work.Be warned that it relies onthe undocumenteddetail that the internal implementation of spin_mutex's constructorzero-initializes it. Though this detail could change in the future, I'd be amazed if it did.

Here is thecode:
[cpp]#include "tbb/spin_mutex.h"
#include "tbb/aligned_space.h"

void func() {
    if( some_condition ) {
        static tbb::aligned_space<:SPIN_MUTEX> mutex;
        tbb::spin_mutex::scoped_lock( *mutex.begin() );
        // the rest of the if clause is now protected
    }
}
[/cpp]

The aligned_space object will be zero-initialized at compile time, thus avoiding a race to initialize it. This trick also works with the current implementation of spin_rw_mutex.cpp. Indeed we use the hack in src/tbb/observer_proxy.cpp.

Ordinarily, I do not like to use hacks that depend on undocumented features. However, the stated problem is a tough nut to crack without thread-safe initialization of function-local static variables.

A mutex that is safe to use in these circumstances and uses only documented TBB features is possible to build. Use a tbb::atomic<:BOOL> and compare_and_swap. To reduce busy-waiting contention, a thread should execute std::this_thread::yield() [defined in "/tbb/compat/thread"] after each failed compare_and_swap.
Andrey_Marochko
New Contributor III
1,349 Views

I think that aligned_space is superfluous in this case, as any static object is guaranteed to be statically zero-initialized.

Actually most of the modern compilers ensure correct one-time initialization of local static objects, but this obviously comes with cost, which certainly makes their usage less efficient in comparison with global static ones.
ARCH_R_Intel
Employee
1,349 Views

The point of the aligned_space is to prevent the constructor of spin_mutex from running after some thread has already acquired a lock on the spin_mutex, in which case the zeroing would cause the lock to be spuriously released. Of course this is only an issue for compilers that do not do thread-safe initialization of local-static objects.
RafSchietekat
Black Belt
1,349 Views

Is it really more than just a test of a status field on x86, which implicitly orders loads and stores, with initialisation-time cost amortised to zero? Other architectures may notice a bump in the road each time they have to explicitly load-acquire the status field. Or did I overlook something?

And what modern compilers do not support it?

ARCH_R_Intel
Employee
1,349 Views

There is a way to avoid the load-acquire on the common path. See appendix in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2382.html .

I can't speak for current compilers. I remember implementing thread-safe function-local static initializations in the KAI C++ compiler back in the 1990s :-)
RafSchietekat
Black Belt
1,349 Views

Thanks, some light bed-time reading... :-)

Althoughwith that declaration I'm a bit surprised that you were also the one who proposed a solution that is only needed with compilers obviously at leastsomething like 15years behind the state of the art (wasn't there an ice age somewhere between then and now in IT time?).
Reply