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
互斥锁是多余的。
没有预自增原子操作(当然,您可以返回前一个值并加一)。
正如皮特所指出的,您的第一个代码块尝试进行预增量(返回增量的结果)。
这样做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,因此上面会导致常量初始化。
否则,您希望将其作为包装该类的静态成员,并将初始化放在其他地方。
您的两个代码段做了两件不同的事情.
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)