如何使这个函数线程安全和快速
How to make this function thread safe and fast?
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;
}
如果你有一个兼容c++11的编译器
(例如NOT VS2013)
两者都有,最简单和最有效的方法就是写:
int f(int x) {
static int firstArg = x;
return firstArg*x;
}
c++11标准要求函数局部静态变量的初始化是线程安全的。更准确地说,它要求只有一个线程初始化变量,所有其他线程等待,直到初始化完成(当然,以后的读写仍然可以竞争,但由于这是对firstArg
的唯一写访问,因此这里不需要额外的同步)。
如果你的编译器不支持"magic static "
下一个最好的方法是使用Sebastian Redl建议的std::call_once
,它具有相同的语义。
如果通过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
是一个不可能作为有效参数传递给f的值。在这种情况下,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;
}
两个重要部分是:
- 做双重检查(一次在保护区内,一次在保护区外)
- 使用
std::atomic
作为标志。否则,不锁定的线程观察标志和变量存储的顺序将得不到保证。
确认:
双重检查锁定版本是基于Herb Sutter在cppcon2014上的演讲,并根据EOF和Sebastian的评论/回答进行了扩展。
*)参见这个问题和c++14标准的最新工作草案(6.7点4):
如果控制在变量初始化时并发进入声明,则并发执行应等待初始化完成。
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;
}
如果您仍然考虑使用某种锁来实现,请尝试自旋锁。它将线程保持在用户空间中,如果操作非常快,则不会切换到内核空间
#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;
}
- 从不同线程使用int64的不同字节安全吗
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 在std::thread中,joinable()然后join()线程安全吗
- 在c++队列中使用pop和visit实现线程安全
- 以线程安全的方式调用"QQuickPaintedItem::updateImage(const QImage&image)"(no QThread)
- 全局变量 多读取器 一个写入器多线程安全?
- 共享队列的线程安全
- boost::文件系统::recursive_directory_iterator多线程安全
- 静态 constexpr 类成员变量对多线程读取是否安全?
- 以线程安全的方式转换 C/C++ 中时区名称字符串的时区偏移量
- 线程安全运算符<<
- 如何使缓存线程安全
- C++线程安全:如果只有一个线程可以写入非原子变量,但多个线程从中读取. 会遇到问题吗?
- 提升精神 V2 Qi 语法线程安全吗?
- 线程调用的函数对对象删除是否安全?
- asio 链对象线程安全吗?
- 线程安全队列 c++
- 提供对不同类型的数据(建议、代码审查)的线程安全访问的类
- 有没有更好的方法可以使此代码线程安全?线程局部静态似乎是一个生硬的工具