int f(int);
Multiple threads can call this function. The function should return
argument * argument_used_in_first_call_to_function
I have coded as below. Even though it is thread-safe it is not fast as it uses mutex lock/unlock. Is there a faster solution while still being thread safe?
mutex mut1;
int f(int x)
{
pthread_mutex_lock(mut1);
static bool first_init = true;
static int first_arg = 0;
if (first_init)
{
first_arg = x;
first_init = false;
}
pthread_mutex_unlock(mut1);
return x * first_arg;
}
IF you have a c++11 compliant compiler
(e.g. NOT VS2013)
Both, the easiest and most efficient way is to just write:
int f(int x) {
static int firstArg = x;
return firstArg*x;
}
The c++11 standard requires that initialization of function local static variables is thread safe *). To be more precise, it requires that only one thread initializes the variable and that all other threads wait, until the initialization is completed (later reads and writes can of course still race, but as this is the only write access to firstArg
, no additional synchronization is required here).
If your compiler doesn't support "magic statics"
The next best method is to use std::call_once
as suggested by Sebastian Redl, which has the same semantics.
If initialization via std::call_once
is too slow (it probably uses a mutex) and arg
is a built-in type (like int), you can try the following (I didn't do any measurements):
namespace {
const int DISALLOWED_VALUE = std::numeric_limits<int>::max();
std::atomic<int> firstArg= DISALLOWED_VALUE;
}
int f(int x) {
if (firstArg.load(std::memory_order_relaxed) == DISALLOWED_VALUE) {
int tmp = DISALLOWED_VALUE;
firstArg.compare_exchange_strong(tmp, x);
}
return firstArg.load(std::memory_order_relaxed)*x;
}
DISALLOWED_VALUE
is some value that can not possibly be passed to f as a valid argument. In this case std::numeric_limits<int>::max()
would cause integer overflow when multiplied with itself, so it is not a valid argument for f
and can thus serve as an indicator that firstArg
hast not been initialized yet.
Warning: Only use this if you have verified, that std::call_once
is unacceptably slow for your particular workload (which will almost never be the case) and that this version is actually a sufficient improvement.
Note on conditional locking
As there are/were some answers that proposed various faulty conditional locking algorithms, I also put up a correct manual implementation of double checked locking.
namespace {
std::atomic<bool> isInit = false; //has to be atomic
std::mutex mux;
}
int f(int x) {
static int firstArg;
if (!isInit.load(std::memory_order_acquire)) {
std::lock_guard<std::mutex> lg(mux);
if (!isInit.load(std::memory_order_acquire)) {
firstArg = x;
isInit.store(true,std::memory_order_release);
}
}
return firstArg*x;
}
The two important parts are:
std::atomic
for the flag. Otherwise, the order, in which threads that don't lock observe the stores to the flag and the variable, is not guaranteed. Acknowledgements:
The double checked locking version is based on the presentation by Herb Sutter on cppcon2014 and was augmented based on the comments/answers by EOF and Sebastian.
*) See e.g. this question and from the latest working draft of the c++14 standard (6.7 point 4):
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
Mike's magic static answer is the best if your compiler supports them. If you're on Visual Studio 2013, the best way to do it is to use std::call_once
, not custom flags and mutexes.
#include <mutex>
namespace {
std::once_flag fFirstCallFlag;
}
int f(int arg) {
static int firstValue;
std::call_once(fFirstCallFlag, [&firstValue, arg] { firstValue = arg; });
return firstValue * arg;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With