简体   繁体   English

原子增量和返回计数器

[英]atomic increment and return counter

Trying to make a unique id generating function, and came up with this:试图制作一个独特的 id 生成函数,并想出了这个:

std::atomic<int> id{0};
int create_id() {
    id++;
    return id.load();
}

But I assume it's possible for that function to return the same value twice, right?但我认为该函数有可能两次返回相同的值,对吗? For example, thread A calls the function, increments the value, but then halts while thread B comes in and also increments the value, finally A and B both return the same value.例如,线程 A 调用该函数,增加值,但在线程 B 进入并增加值时暂停,最后 A 和 B 都返回相同的值。

So using mutexes, the function might look like this:因此,使用互斥锁,该函数可能如下所示:

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

My question: Is it possible to create the behavior of spawning unique int values from a counter using only atomics?我的问题:是否可以仅使用原子来创建从计数器生成唯一 int 值的行为? The reason I'm asking is because I need to spawn a lot of id's, but read that mutex is slow.我问的原因是因为我需要生成很多 id,但是读到互斥锁很慢。

Simply use:只需使用:

std::atomic<int> id;

int create_id() {
    return id++;
}

See http://en.cppreference.com/w/cpp/atomic/atomic/operator_arith请参阅http://en.cppreference.com/w/cpp/atomic/atomic/operator_arith

Your two code snippets do two different things.你的两个代码片段做了两件不同的事情。

id++;
return id.load();

that code increments id , then returns the incremented value.该代码增加id ,然后返回增加的值。

std::lock_guard<std::mutex> lock{mx};
return id++;

that code returns the value before the increment.该代码返回增量的值。

The correct code to do what the first tries to do is做第一个尝试做的正确代码是

return ++id;

The correct code to do what the second does is执行第二个操作的正确代码是

return id++;

A mutex is overkill.互斥锁是矫枉过正的。

There is no pre-increment atomic operation (but you can return the previous value and add one to that, of course).没有预增量原子操作(当然,您可以返回前一个值并为其添加一个)。

As pointed out by Pete, your first code block attempts to do a pre-increment (return the result of the increment).正如 Pete 所指出的,您的第一个代码块尝试进行预增量(返回增量的结果)。

Doing return ++id works, but is equivalent to return id.执行return ++id有效,但等效于return id. fetch_add (1) + 1; fetch_add (1) + 1; which uses the slow default sequentially-consistent memory order.它使用慢速默认顺序一致的内存顺序。 This is not required here, in fact you can do with a relaxed memory order .这在这里不是必需的,实际上您可以使用轻松的内存顺序

If you really mean to use a global variable for the atomic, the correct (and fastest) code that does what your first code block attempts is:如果您真的想为原子使用全局变量,那么执行第一个代码块尝试的正确(和最快)代码是:

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

Notes:笔记:

You can leave away the + 1 if you want post-increment.如果您想要后增量,您可以省略+ 1

Using std::memory_relaxed doesn't make a difference on Intel CPU's (x86) because fetch_add is a Read-Modify-Write operation and the bus must be locked anyway ( lock assembly instruction).使用std::memory_relaxed对 Intel CPU (x86) 没有影响,因为fetch_add是读取-修改-写入操作,并且无论如何都必须锁定总线( lock汇编指令)。 But on a more relaxed architecture it does make a difference.但在更轻松的架构上,它确实有所作为。

I didn't want to pollute global namespace with 'id', so I put it as a static in the function;我不想用 'id' 污染全局命名空间,所以我把它作为一个静态函数放在函数中; however in that case you must make sure that on your platform that doesn't lead to actual initialization code.但是在这种情况下,您必须确保在您的平台上不会导致实际的初始化代码。 Eg if a constructor that isn't constexpr needs to be called then a test is necessary to see if the static was already initialized or not.例如,如果需要调用不是 constexpr 的构造函数,则需要进行测试以查看静态是否已经初始化。 Fortunately, the value initializing constructor of an integral atomic is constexpr, so the above leads to constant initialization .幸运的是,整型原子的值初始化构造函数是 constexpr,因此上面导致了常量初始化

Otherwise you'd want to make it -say- a static member of a class that is wrapping this and put the initialization somewhere else.否则,您可能希望使其成为包装它的类的静态成员,并将初始化放在其他地方。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM