简体   繁体   English

C ++线程安全整数

[英]C++ Thread Safe Integer

I have currently created a C++ class for a thread safe integer which simply stores an integer privately and has public get a set functions which use a boost::mutex to ensure that only one change at a time can be applied to the integer. 我当前为线程安全整数创建了一个C ++类,它只是私有地存储一个整数,并且公共获取一个使用boost :: mutex的set函数,以确保一次只能对整数应用一个更改。

Is this the most efficient way to do it, I have been informed that mutexes are quite resource intensive? 这是最有效的方法,我被告知互斥量是非常耗费资源的吗? The class is used a lot, very rapidly so it could well be a bottleneck... 该课程使用频繁,非常迅速,因此很可能成为瓶颈......

Googleing C++ Thread Safe Integer returns unclear views and oppinions on the thread safety of integer operations on different architectures. 谷歌C ++线程安全整数返回不清楚的视图和oppinions在不同架构上的整数操作的线程安全性。

Some say that a 32bit int on a 32bit arch is safe, but 64 on 32 isn't due to 'alignment' Others say it is compiler/OS specific (which I don't doubt). 有人说32位拱门上的32位int是安全的,但是32位上的64位并不是由于“对齐”。其他人说它是编译器/操作系统特定的(我不怀疑)。

I am using Ubuntu 9.10 on 32 bit machines, some have dual cores and so threads may be executed simultaneously on different cores in some cases and I am using GCC 4.4's g++ compiler. 我在32位机器上使用Ubuntu 9.10,有些具有双核,因此在某些情况下可以在不同的内核上同时执行线程,我使用的是GCC 4.4的g ++编译器。

Thanks in advance... 提前致谢...

Please Note: The answer I have marked as 'correct' was most suitable for my problem - however there are some excellent points made in the other answers and they are all worth reading! 请注意: 我标记为“正确”的答案最适合我的问题 - 但是在其他答案中有一些优点,它们都值得一读!

有C ++ 0x原子库,还有一个正在开发的Boost.Atomic库,它使用无锁技术。

It's not compiler and OS specific, it's architecture specific. 它不是编译器和操作系统特定的,它是特定于体系结构的。 The compiler and OS come into it because they're the tools you work through, but they're not the ones setting the real rules. 编译器和操作系统进入它,因为它们是你工作的工具,但它们不是设置真实规则的工具。 This is why the C++ standard won't touch the issue. 这就是为什么C ++标准不会触及这个问题的原因。

I have never in my life heard of an 64-bit integer write, which can be split into two 32-bit writes, being interrupted halfway through. 我一生中从未听说过64位整数写入,可以将其分成两个32位写入,中途被中断。 (Yes, that's an invitation to others to post counterexamples.) Specifically, I have never heard of a CPU's load/store unit allowing a misaligned write to be interrupted; (是的,这是邀请其他人发布反例的。)具体来说,我从来没有听说过CPU的加载/存储单元允许错位的写入被中断; an interrupting source has to wait for the whole misaligned access to complete. 中断源必须等待整个未对齐的访问完成。

To have an interruptible load/store unit, its state would have to be saved to the stack... and the load/store unit is what saves the rest of the CPU's state to the stack. 要拥有可中断的加载/存储单元,必须将其状态保存到堆栈中......并且加载/存储单元将CPU的其余状态保存到堆栈中。 This would be hugely complicated, and bug prone, if the load/store unit were interruptible... and all that you would gain is one cycle less latency in responding to interrupts, which, at best, is measured in tens of cycles. 如果加载/存储单元是可中断的,那么这将非常复杂,并且容易出错......并且您将获得的所有响应中断的延迟减少一个周期 ,最多只能在数十个周期内测量。 Totally not worth it. 完全不值得。

Back in 1997, A coworker and I wrote a C++ Queue template which was used in a multiprocessing system. 早在1997年,一位同事和我编写了一个用于多处理系统的C ++队列模板。 (Each processor had its own OS running, and its own local memory, so these queues were only needed for memory shared between processors.) We worked out a way to make the queue change state with a single integer write, and treated this write as an atomic operation. (每个处理器都有自己的OS运行,以及它自己的本地内存,所以这些队列只需要处理器之间共享的内存。)我们找到了一种方法,用一个整数写入来改变队列状态,并将此写入视为原子操作。 Also, we required that each end of the queue (ie the read or write index) be owned by one and only one processor. 此外,我们要求队列的每一端(即读或写索引)由一个且仅一个处理器拥有。 Thirteen years later, the code is still running fine, and we even have a version that handles multiple readers. 十三年后,代码仍然运行良好,我们甚至有一个处理多个读者的版本。

Still, if you want to treat a 64-bit integer write as atomic, align the field to a 64-bit bound. 但是,如果要将64位整数写为原子,请将该字段与64位边界对齐。 Why worry? 为什么要担心?

EDIT: For the case you mention in your comment, I'd need more information to be sure, so let me give an example of something that could be implemented without specialized synchronization code. 编辑:对于你在评论中提到的情况,我需要更多的信息来确定,所以让我举一个可以在没有专门的同步代码的情况下实现的东西的例子。

Suppose you have N writers and one reader. 假设你有N个作家和一个读者。 You want the writers to be able to signal events to the reader. 您希望编写者能够向读者发送事件信号。 The events themselves have no data; 事件本身没有数据; you just want an event count, really. 你只是想要一个事件计数,真的。

Declare a structure for the shared memory, shared between all writers and the reader: 声明共享内存的结构,在所有作者和读者之间共享:

#include <stdint.h>
struct FlagTable
{   uint32_t flag[NWriters];
};

(Make this a class or template or whatever as you see fit.) (将其设为类或模板或您认为合适的任何内容。)

Each writer needs to be told its index and given a pointer to this table: 需要告诉每个编写器它的索引并给出一个指向该表的指针:

class Writer
{public:
    Writer(FlagTable* flags_, size_t index_): flags(flags_), index(index_) {}
    void SignalEvent(uint32_t eventCount = 1);
private:
    FlagTable* flags;
    size_t index;
}

When the writer wants to signal an event (or several), it updates its flag: 当作者想要发出一个或多个事件的信号时,它会更新其标志:

void Writer::SignalEvent(uint32_t eventCount)
{   // Effectively atomic: only one writer modifies this value, and
    // the state changes when the incremented value is written out.
    flags->flag[index] += eventCount;
}

The reader keeps a local copy of all the flag values it has seen: 读者保留它看到的所有标志值的本地副本:

class Reader
{public:
    Reader(FlagTable* flags_): flags(flags_)
    {   for(size_t i = 0; i < NWriters; ++i)
            seenFlags[i] = flags->flag[i];
    }
    bool AnyEvents(void);
    uint32_t CountEvents(int writerIndex);
private:
    FlagTable* flags;
    uint32_t seenFlags[NWriters];
}

To find out if any events have happened, it just looks for changed values: 要查明是否发生了任何事件,它只会查找更改的值:

bool Reader::AnyEvents(void)
{   for(size_t i = 0; i < NWriters; ++i)
        if(seenFlags[i] != flags->flag[i])
            return true;
    return false;
}

If something happened, we can check each source and get the event count: 如果发生了什么事,我们可以检查每个来源并获得事件计数:

uint32_t Reader::CountEvents(int writerIndex)
{   // Only read a flag once per function call.  If you read it twice,
    // it may change between reads and then funny stuff happens.
    uint32_t newFlag = flags->flag[i];
    // Our local copy, though, we can mess with all we want since there
    // is only one reader.
    uint32_t oldFlag = seenFlags[i];
    // Next line atomically changes Reader state, marking the events as counted.
    seenFlags[i] = newFlag;
    return newFlag - oldFlag;
}

Now the big gotcha in all this? 现在这一切都很重要吗? It's nonblocking, which is to say that you can't make the Reader sleep until a Writer writes something. 它是非阻塞的,也就是说,在Writer写东西之前你不能让读者睡觉。 The Reader has to choose between sitting in a spin-loop waiting for AnyEvents() to return true , which minimizes latency, or it can sleep a bit each time through, which saves CPU but could let a lot of events build up. 读者必须在旋转循环中进行选择,等待AnyEvents()返回true ,这样可以最大限度地减少延迟,或者每次都可以睡一会儿,这可以节省CPU,但可以让很多事件积累起来。 So it's better than nothing, but it's not the solution to everything. 所以它总比没有好,但它并不是解决所有问题的方法。

Using actual synchronization primitives, one would only need to wrap this code with a mutex and condition variable to make it properly blocking: the Reader would sleep until there was something to do. 使用实际的同步原语,只需要使用互斥锁和条件变量来包装此代码以使其正确阻塞:读取器将睡眠直到有事情要做。 Since you used atomic operations with the flags, you could actually keep the amount of time the mutex is locked to a minimum: the Writer would only need to lock the mutex long enough to send the condition, and not set the flag, and the reader only needs to wait for the condition before calling AnyEvents() (basically, it's like the sleep-loop case above, but with a wait-for-condition instead of a sleep call). 由于您使用带有标志的原子操作,实际上可以将互斥锁锁定的时间保持在最小值:Writer只需要锁定互斥锁足够长的时间来发送条件,而不是设置标志和读取器只需要在调用AnyEvents()之前等待条件(基本上,它就像上面的睡眠循环情况,但是等待条件而不是睡眠调用)。

C++ has no real atomic integer implementation, neither do most common libraries. C ++没有真正的原子整数实现,大多数常见的库也没有。

Consider the fact that even if said implementation would exist, it would have to rely on some sort of mutex - due to the fact that you cannot guarantee atomic operations across all architectures. 考虑这样一个事实,即使所述实现存在,它也必须依赖某种互斥体 - 因为你无法保证所有体系结构的原子操作。

As you're using GCC, and depending on what operations you want to perform on the integer, you might get away with GCC's atomic builtins . 当您使用GCC时,根据您要对整数执行的操作,您可能会使用GCC的原子内置函数

These might be a bit faster than mutexes, but in some cases still a lot slower than "normal" operations. 这些可能比互斥锁快一点,但在某些情况下仍然比“正常”操作慢很多。

For full, general purpose synchronization, as others have already mentioned, the traditional synchronization tools are pretty much required. 对于完整的通用同步,正如其他人已经提到的那样,传统的同步工具是非常需要的。 However, for certain special cases it is possible to take advantage of hardware optimizations. 但是,对于某些特殊情况,可以利用硬件优化。 Specifically, most modern CPUs support atomic increment & decrement on integers. 具体来说,大多数现代CPU支持整数上的原子递增和递减。 The GLib library has pretty good cross-platform support for this. GLib库对此有很好的跨平台支持。 Essentially, the library wraps CPU & compiler specific assembly code for these operations and defaults to mutex protection where they're not available. 本质上,库为这些操作包装CPU和编译器特定的汇编代码,默认为无法使用的互斥保护。 It's certainly not very general-purpose but if you're only interested in maintaining a counter, this might be sufficient. 它当然不是非常通用的,但如果你只想维持一个计数器,这可能就足够了。

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

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