尝试创建一个唯一的id生成函数,并提出了这个:
std::atomic<int> id{0};
int create_id() {
id++;
return id.load();
}
但是我认为该函数可以两次返回相同的值,对吧?例如,线程A调用函数,递增值,但随后在线程B进入时停止并递增值,最后A和B都返回相同的值。
因此,使用互斥锁,该函数可能如下所示:
std::mutex mx;
int id = 0;
int create_id() {
std::lock_guard<std::mutex> lock{mx};
return id++;
}
我的问题:是否可以仅使用原子创建从计数器生成唯一int值的行为?我问的原因是因为我需要产生很多id,但是读到mutex很慢。
答案 0 :(得分:26)
只需使用:
std::atomic<int> id;
int create_id() {
return id++;
}
请参阅http://en.cppreference.com/w/cpp/atomic/atomic/operator_arith
答案 1 :(得分:6)
您的两个代码段做了两件不同的事情。
module
该代码递增id++;
return id.load();
,然后返回递增的值。
id
该代码在增量之前返回值。
执行第一次尝试做的正确代码是
std::lock_guard<std::mutex> lock{mx};
return id++;
执行第二项操作的正确代码是
return ++id;
答案 2 :(得分:1)
互斥锁过大。
没有预递增的原子操作(但是您可以返回 之前的值,然后再添加一个。)
正如Pete指出的那样,您的第一个代码块尝试执行 预先增加(返回增加的结果)。
执行return ++id
可行,但等效于return id.
fetch_add
(1) + 1;
它使用缓慢的默认顺序一致的内存顺序。这里不需要这样做,实际上您可以轻松地使用memory order。
如果您真的想对原子使用全局变量,那么执行您的第一个代码块尝试的正确(且最快)的代码是:
int create_id() {
static std::atomic<int> id{0};
return id.fetch_add(1, std::memory_order_relaxed) + 1;
}
注意:
如果要后递增,可以不使用+ 1
。
使用std::memory_relaxed
在Intel CPU(x86)上没有什么区别,因为fetch_add
是一个Read-Modify-Write操作,并且总线必须始终锁定(lock
汇编指令)。但是在更轻松的架构上,它确实有所作为。
我不想用'id'污染全局名称空间,因此我将其作为静态变量放入函数中;但是,在这种情况下,必须确保平台上不会导致实际的初始化代码。例如。如果需要调用不是constexpr的构造函数,则需要进行测试以查看static是否已初始化。幸运的是,整数原子的值初始化构造函数是constexpr,因此上面的内容导致生成constant initialization。
否则,您需要使其成为-say-类的静态成员,该类将其包装并将初始化放在其他位置。