int f(int);
多个线程可以调用此函数。 该函数应该返回
argument * argument_used_in_first_call_to_function
我编码如下。尽管它是线程安全的,但它并不快,因为它使用互斥锁定/解锁。在保持线程安全的同时是否有更快的解决方案?
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;
}
答案 0 :(得分:12)
如果您有符合c ++ 11标准的编译器
(例如,不是VS2013)
两者,最简单,最有效的方法就是写:
int f(int x) {
static int firstArg = x;
return firstArg*x;
}
c ++ 11标准要求函数本地静态变量的初始化是线程安全的*)。更确切地说,它要求只有一个线程初始化变量并且所有其他线程都等待,直到初始化完成(稍后读取和写入当然仍然可以竞争,但因为这是对{{1}的唯一写入访问权限},这里不需要额外的同步。
如果您的编译器不支持" magic statics"
下一个最好的方法是使用Sebastian Redl建议的firstArg
,它具有相同的语义。
如果通过std::call_once
初始化太慢(它可能使用互斥锁)并且std::call_once
是内置类型(如int),您可以尝试以下(我没有做过)做任何测量):
arg
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;
}
是一些不能作为有效参数传递给f的值。在这种情况下,DISALLOWED_VALUE
会在与自身相乘时导致整数溢出,因此它不是std::numeric_limits<int>::max()
的有效参数,因此可以作为f
尚未初始化的指示符。
警告:只有在经过验证后才能使用此功能,firstArg
对于您的特定工作负载来说速度慢得令人无法接受(几乎不会出现这种情况)并且此版本实际上已足够改善。
关于条件锁定的说明
由于有一些答案提出了各种错误的条件锁定算法,我还提出了双重检查锁定的正确手动实现。
std::call_once
两个重要部分是:
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;
}
作为标志。否则,不保证不会保证锁定的线程观察存储到标志和变量的顺序。 致谢:
双重检查锁定版本基于Herb Sutter在cppcon2014上的演示,并根据EOF和Sebastian的评论/答案进行了扩充。
*)参见例如this question以及c ++ 14标准的最新工作草案(6.7第4点):
如果控件在初始化变量时同时进入声明,则并发执行应等待初始化完成。
答案 1 :(得分:6)
如果您的编译器支持,那么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 :(得分:1)
如果仍然考虑使用某些锁实现,请尝试使用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;
}