2010-07-29 56 views
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); 
} 

的数据被窃取的指针,它保护每一个读取和写入,这应该使线程安全的,但它需要每一个访问提供了两个互锁的指令。将有大量的读取和写入,并且我不能预先告知是否会有更多的读取或更多的写入。

它可以做得比这更有效吗?这在阅读时也会锁定,但由于可能有更多的写入,所以阅读没有任何优化阅读的意义,除非它不会对写作造成损失。

我正在考虑在没有互锁指令(连同序列号)的情况下获取指针的读取操作,复制数据,然后有一种方法告诉序列号是否已更改,在这种情况下应该重试。但是,这需要一些记忆障碍,我不知道它是否能够提高速度。

-----编辑-----

感谢所有的好评!我实际上没有运行这个代码,但我会尝试今天晚些时候将当前方法与关键部分进行比较(如果我获得时间)。我仍然在寻找最佳的解决方案,所以稍后我会回到更高级的评论。再次感谢!

+0

什么是使用默认的线程同步原语的问题? – naivnomore 2010-07-29 06:29:07

+0

我必须承认,我只是假设我能做得更快。 1)我只在这里显示一个实例,但实际上我可能会有这些受保护的数据记录的10000个实例,这将意味着10000个关键部分。但也许这不是问题,我不知道,我从来没有尝试过这样的事情。 2)我希望我能拿出比关键部分更快的东西。每秒可以轻松实现数百万次读/写。而在个人层面上,我认为让它像人性化(机器化)一样快是有趣的。 – Rabbit 2010-07-29 06:49:06

+0

Windows CRITICAL_SECTION非常轻便,除非实际上必须阻止。我不认为像这样忙着等待用户线程是一个非常好的主意 - 你隐式地告诉调度程序,你有很多事情要做,而实际上情况正好相反。 – 2010-07-29 07:46:54

回答

3

你写的东西基本上是螺旋锁。如果你打算这么做,那么你可能只需要使用一个互斥体,比如boost::mutex。如果你真的想要一个自旋锁,使用一个系统提供的,或者一个库,而不是自己写。

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

+0

没错,它是一个螺旋锁。经过一番研究后,似乎可以在退出时不需要锁定指令就可以实现自旋锁,并且在旋转时不需要写任何东西。这可能是我正在寻找的解决方案。我现在很忙,但稍后我会回头看看。 – Rabbit 2010-07-29 08:57:51

+0

到目前为止,我的最佳解决方案是使用我需要保护的每个指针的自旋锁。它们每个只使用4个字节。它与本问题中的示例代码基本相同,区别在于自旋锁在旋转时不会使用互锁指令,并且它使用asm暂停指令。 – Rabbit 2010-07-29 19:21:53

2

有几种方法可以解决这个问题,特别是没有互斥或锁定机制。问题是我不确定你的系统上的约束是什么。

请记住,原子操作是经常被C++编译器移动的东西。通过在每个写线程1单生产者单消费者

多生产单消费:

一般来说,我会解决这样的问题。每个线程写入他们自己的队列中。单个消费者线程收集生成的数据并将其存储在单个消费者多读取器数据存储中。对此的实现是很多工作,只有在您正在做一个时间关键型应用程序并且您有时间投入此解决方案时才推荐。

还有更多的事情来读了这一点,因为执行是特定于平台:

原子等在Windows/XBOX360操作: http://msdn.microsoft.com/en-us/library/ee418650(VS.85).aspx

多线程的单生产者单消费者无锁:
http://www.codeproject.com/KB/threads/LockFree.aspx#heading0005

什么 “挥发性” 还真是,可以用于:
http://www.drdobbs.com/cpp/212701484

香草萨特写了,提醒你写这种代码的危险的好文章: http://www.drdobbs.com/cpp/210600279;jsessionid=ZSUN3G3VXJM0BQE1GHRSKHWATMY32JVN?pgno=2

+0

另一个非锁定队列的优秀解决方案: http://www.drdobbs.com/architecture-and-design/210604448 – Simon 2010-07-29 07:54:52

+0

请注意比赛条件。 – Simon 2010-07-29 07:56:56