原子增量和返回计数器

Ano*_*ity 17 c++ multithreading

试图创建一个唯一的id生成函数,并想出了这个:

std::atomic<int> id{0};
int create_id() {
    id++;
    return id.load();
}
Run Code Online (Sandbox Code Playgroud)

但我认为该函数可能两次返回相同的值,对吧?例如,线程A调用该函数,递增该值,但随后在线程B进入时停止并且还递增该值,最后A和B都返回相同的值.

所以使用互斥锁,函数可能如下所示:

std::mutex mx;
int id = 0;
int create_id() {
    std::lock_guard<std::mutex> lock{mx};
    return id++;
}
Run Code Online (Sandbox Code Playgroud)

我的问题:是否有可能仅使用原子创建从计数器生成唯一int值的行为?我问的原因是因为我需要产生很多id,但是读到mutex很慢.

Jar*_*d42 32

只需使用:

std::atomic<int> id;

int create_id() {
    return id++;
}
Run Code Online (Sandbox Code Playgroud)

http://en.cppreference.com/w/cpp/atomic/atomic/operator_arith

  • 默认情况下,std :: atomic <int>未初始化.由于此特定实例具有静态存储持续时间,因此将其初始化为0,但如果`id`是类中的字段,则需要在`std :: atomic <int>之后添加`{0}` id` (11认同)

Car*_*ood 9

互斥锁是多余的。

没有预自增原子操作(当然,您可以返回前一个值并加一)。

正如皮特所指出的,您的第一个代码块尝试进行预增量(返回增量的结果)。

这样做return ++id有效,但相当于 使用慢速默认顺序一致内存顺序。这里不需要这样做,事实上您可以使用宽松的内存顺序来完成。return id.fetch_add(1) + 1;

如果您确实打算使用原子全局变量,则执行第一个代码块尝试的正确(且最快)代码是:

int create_id() {
    static std::atomic<int> id{0};
    return id.fetch_add(1, std::memory_order_relaxed) + 1;
}
Run Code Online (Sandbox Code Playgroud)

笔记:

+ 1如果您想要后增量,您可以忽略。

使用std::memory_relaxed在 Intel CPU (x86) 上没有什么区别,因为它fetch_add是读取-修改-写入操作,而且无论如何总线都必须锁定(lock汇编指令)。但在更轻松的架构上,它确实有所不同。

我不想用“id”污染全局命名空间,所以我把它作为函数中的静态变量;但是在这种情况下,您必须确保在您的平台上这不会导致实际的初始化代码。例如,如果需要调用不是 constexpr 的构造函数,则需要进行测试以查看静态是否已初始化。幸运的是,整型原子的值初始化构造函数是 constexpr,因此上面会导致常量初始化

否则,您希望将其作为包装该类的静态成员,并将初始化放在其他地方。


Pet*_*ker 6

您的两个代码段做了两件不同的事情.

id++;
return id.load();
Run Code Online (Sandbox Code Playgroud)

该代码递增id,然后返回递增的值.

std::lock_guard<std::mutex> lock{mx};
return id++;
Run Code Online (Sandbox Code Playgroud)

该代码返回增量的值.

做第一次尝试做的正确代码是

return ++id;
Run Code Online (Sandbox Code Playgroud)

执行第二个操作的正确代码是

return id++;
Run Code Online (Sandbox Code Playgroud)