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;
}
3 个解决方案
#1
6
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.
如果您的编译器支持它,那么Mike的神奇静态答案是最好的。如果您使用的是Visual Studio 2013,最好的方法是使用std :: call_once,而不是自定义标志和互斥锁。
#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;
}
#2
12
IF you have a c++11 compliant compiler
(e.g. NOT VS2013)
如果你有一个符合c ++ 11的编译器(例如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).
c ++ 11标准要求函数本地静态变量的初始化是线程安全的*)。更确切地说,它要求只有一个线程初始化变量并且所有其他线程都等待,直到初始化完成(稍后读取和写入当然仍然可以竞争,但因为这是对firstArg的唯一写入访问,所以没有额外的这里需要同步)。
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.
下一个最好的方法是使用Sebastian Redl建议的std :: call_once,它具有相同的语义。
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):
如果通过std :: call_once初始化太慢(它可能使用互斥锁)并且arg是内置类型(如int),您可以尝试以下(我没有做任何测量):
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.
DISALLOWED_VALUE是一些不能作为有效参数传递给f的值。在这种情况下,std :: numeric_limits
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.
警告:只有在经过验证后才能使用它,std :: call_once对于您的特定工作负载来说速度慢得令人无法接受(几乎不会出现这种情况),而且这个版本实际上是一个充分的改进。
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:
两个重要部分是:
- Do double checking (once inside and once outside of the protected region)
- Use an
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.
进行双重检查(一次在内部,一次在受保护区域之外)
使用std :: atomic作为标志。否则,无法保证不锁定的线程观察存储到标志和变量的顺序。
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.
致谢:双重检查锁定版本基于Herb Sutter在cppcon2014上的演示,并根据EOF和Sebastian的评论/答案进行了扩充。
*) See e.g. this question and from the latest working draft of the c++14 standard (6.7 point 4):
*)参见例如这个问题以及c ++ 14标准的最新工作草案(6.7分4):
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果控制在初始化变量时同时进入声明,则并发执行应等待初始化完成。
#3
1
If you still consider implementation with some lock, try spinlock. It is keeping the thread in the user space with spin, and is not making switch to kernel space if operations are fast
如果您仍然考虑使用某些锁实现,请尝试使用spinlock。它通过旋转将线程保留在用户空间中,并且如果操作很快,则不会切换到内核空间
#include "boost/smart_ptr/detail/spinlock.hpp"
boost::detail::spinlock lock;
bool first_init = true;
int first_arg = 0;
int f(int x)
{
std::lock_guard<boost::detail::spinlock> guard(lock);
if (first_init)
{
first_arg = x;
first_init = false;
}
return x * first_arg;
}
#1
6
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.
如果您的编译器支持它,那么Mike的神奇静态答案是最好的。如果您使用的是Visual Studio 2013,最好的方法是使用std :: call_once,而不是自定义标志和互斥锁。
#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;
}
#2
12
IF you have a c++11 compliant compiler
(e.g. NOT VS2013)
如果你有一个符合c ++ 11的编译器(例如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).
c ++ 11标准要求函数本地静态变量的初始化是线程安全的*)。更确切地说,它要求只有一个线程初始化变量并且所有其他线程都等待,直到初始化完成(稍后读取和写入当然仍然可以竞争,但因为这是对firstArg的唯一写入访问,所以没有额外的这里需要同步)。
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.
下一个最好的方法是使用Sebastian Redl建议的std :: call_once,它具有相同的语义。
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):
如果通过std :: call_once初始化太慢(它可能使用互斥锁)并且arg是内置类型(如int),您可以尝试以下(我没有做任何测量):
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.
DISALLOWED_VALUE是一些不能作为有效参数传递给f的值。在这种情况下,std :: numeric_limits
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.
警告:只有在经过验证后才能使用它,std :: call_once对于您的特定工作负载来说速度慢得令人无法接受(几乎不会出现这种情况),而且这个版本实际上是一个充分的改进。
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:
两个重要部分是:
- Do double checking (once inside and once outside of the protected region)
- Use an
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.
进行双重检查(一次在内部,一次在受保护区域之外)
使用std :: atomic作为标志。否则,无法保证不锁定的线程观察存储到标志和变量的顺序。
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.
致谢:双重检查锁定版本基于Herb Sutter在cppcon2014上的演示,并根据EOF和Sebastian的评论/答案进行了扩充。
*) See e.g. this question and from the latest working draft of the c++14 standard (6.7 point 4):
*)参见例如这个问题以及c ++ 14标准的最新工作草案(6.7分4):
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果控制在初始化变量时同时进入声明,则并发执行应等待初始化完成。
#3
1
If you still consider implementation with some lock, try spinlock. It is keeping the thread in the user space with spin, and is not making switch to kernel space if operations are fast
如果您仍然考虑使用某些锁实现,请尝试使用spinlock。它通过旋转将线程保留在用户空间中,并且如果操作很快,则不会切换到内核空间
#include "boost/smart_ptr/detail/spinlock.hpp"
boost::detail::spinlock lock;
bool first_init = true;
int first_arg = 0;
int f(int x)
{
std::lock_guard<boost::detail::spinlock> guard(lock);
if (first_init)
{
first_arg = x;
first_init = false;
}
return x * first_arg;
}