我有一些数据由多个线程读取和更新。读取和写入都必须是原子的。我想这样做:
// 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 :(得分:3)
你所写的内容本质上是一个螺旋锁。如果您要这样做,那么您也可以使用互斥锁,例如boost::mutex。如果你真的想要一个自旋锁,请使用系统提供的自旋锁,或者使用库中的一个,而不是自己编写。
其他可能性包括进行某种形式的写时复制。通过指针存储数据结构,只读取读取端的指针(原子)。在写入端然后创建一个新实例(根据需要复制旧数据)并原子地交换指针。如果写入确实需要旧值并且有多个写入器,那么您将需要执行比较交换循环以确保自您读取之后该值未发生更改(请注意ABA问题)或者使用互斥锁作家。如果你这样做,那么你需要小心如何管理内存 - 当没有线程引用它时(但之前没有),你需要一些方法来回收数据的实例。
答案 1 :(得分: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
Herb Sutter写了一篇很好的文章,提醒你写这种代码的危险性: http://www.drdobbs.com/cpp/210600279;jsessionid=ZSUN3G3VXJM0BQE1GHRSKHWATMY32JVN?pgno=2