2

我有一些由多个线程读取和更新的数据。读取和写入都必须是原子的。我正在考虑这样做:

// Values must be read and updated atomically
struct SValues
{
    double a;
    double b;
    double c;
    double d;
};

class Test
{
public:
    Test()
    {
        m_pValues = &m_values;
    }

    SValues* LockAndGet()
    {
        // Spin forver until we got ownership of the pointer
        while (true)
        {
            SValues* pValues = (SValues*)::InterlockedExchange((long*)m_pValues, 0xffffffff);
            if (pValues != (SValues*)0xffffffff)
            {
                return pValues;
            }
        }
    }

    void Unlock(SValues* pValues)
    {
        // Return the pointer so other threads can lock it
        ::InterlockedExchange((long*)m_pValues, (long)pValues);
    }

private:
    SValues* m_pValues;
    SValues m_values;
};

void TestFunc()
{
    Test test;

    SValues* pValues = test.LockAndGet();

    // Update or read values

    test.Unlock(pValues);
}

通过在每次读取和写入时窃取指向它的指针来保护数据,这应该使其成为线程安全的,但每次访问都需要两条互锁指令。将会有大量的读取和写入,我无法提前判断是否会有更多的读取或更多的写入。

能做到比这更有效吗?这在读取时也会锁定,但由于很可能有更多的写入然后读取,因此优化读取没有意义,除非它不会对写入造成惩罚。

我正在考虑在没有互锁指令(以及序列号)的情况下读取指针,复制数据,然后有一种方法来判断序列号是否已更改,在这种情况下它应该重试。不过,这需要一些内存屏障,我不知道它是否可以提高速度。

- - - 编辑 - - -

谢谢大家,很棒的评论!我实际上并没有运行此代码,但我会在今天晚些时候尝试将当前方法与关键部分进行比较(如果我有时间的话)。我仍在寻找最佳解决方案,因此稍后我将返回更高级的评论。再次感谢!

4

2 回答 2

3

你所写的本质上是一个自旋锁。如果您要这样做,那么您不妨只使用互斥锁,例如boost::mutex。如果您真的想要自旋锁,请使用系统提供的自旋锁,或者使用库中的自旋锁,而不是自己编写。

其他可能性包括进行某种形式的写时复制。通过指针存储数据结构,并在读取端(原子地)读取指针。然后在写入端创建一个新实例(根据需要复制旧数据)并原子交换指针。如果写入确实需要旧值并且有多个写入器,那么您将需要执行比较交换循环以确保该值在您读取后没有更改(注意 ABA 问题),或者为作家们。如果你这样做,那么你需要小心你如何管理内存——当没有线程引用它时(但不是之前),你需要一些方法来回收数据的实例。

于 2010-07-29T07:43:07.867 回答
3

There are several ways to resolve this, specifically without mutexes or locking mechanisms. The problem is that I'm not sure what the constraints on your system is.

Remember that atomic operations is something that often get moved around by the compilers in C++.

Generally I would solve the issue like this:

Multiple-producer-single-consumer by having 1 single-producer-single-consumer per writing thread. Each thread writes into their own queue. A single consumer thread that gathers the produced data and stores it in a single-consumer-multiple-reader data storage. The implementation for this is a lot of work and only recommended if you are doing a time-critical application and that you have the time to put in for this solution.

There are more things to read up about this, since the implementation is platform specific:

Atomic etc operations on windows/xbox360: http://msdn.microsoft.com/en-us/library/ee418650(VS.85).aspx

The multithreaded single-producer-single-consumer without locks:
http://www.codeproject.com/KB/threads/LockFree.aspx#heading0005

What "volatile" really is and can be used for:
http://www.drdobbs.com/cpp/212701484

Herb Sutter has written a good article that reminds you of the dangers of writing this kind of code: http://www.drdobbs.com/cpp/210600279;jsessionid=ZSUN3G3VXJM0BQE1GHRSKHWATMY32JVN?pgno=2

于 2010-07-29T07:48:16.990 回答