原子增量和返回计数器

时间:2016-12-18 09:00:29

标签: c++ multithreading

尝试创建一个唯一的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很慢。

3 个答案:

答案 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-类的静态成员,该类将其包装并将初始化放在其他位置。