【发布时间】:2010-07-29 05:53:20
【问题描述】:
我有一些数据由多个线程读取和更新。读取和写入都必须是原子的。我正在考虑这样做:
// 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);
}
通过在每次读写时窃取指向它的指针来保护数据,这应该使其成为线程安全的,但每次访问都需要两条互锁指令。将会有大量的读取和写入,我无法提前知道是否会有更多的读取或更多的写入。
可以做得比这更有效吗?这在读取时也会锁定,但由于很可能有更多的写入然后读取,因此优化读取没有意义,除非它不会对写入造成惩罚。
我正在考虑在没有互锁指令(以及序列号)的情况下读取指针,复制数据,然后有一种方法来判断序列号是否已更改,在这种情况下它应该重试。不过,这需要一些内存屏障,我不知道它是否可以提高速度。
----- 编辑-----
谢谢大家,伟大的 cmets!我实际上并没有运行此代码,但我会在今天晚些时候尝试将当前方法与关键部分进行比较(如果我有时间的话)。我仍在寻找最佳解决方案,因此稍后我将回到更高级的 cmets。再次感谢!
【问题讨论】:
-
使用默认线程同步原语有什么问题?
-
我必须承认我只是假设我可以做得比这更快。 1) 我在这里只显示一个实例,但实际上我可能会有 10000 个这些受保护数据记录的实例,这意味着 10000 个关键部分。但也许这不是问题,我不知道,我从未尝试过这样的事情。 2)我希望我能比关键部分更快地想出一些东西。每秒很容易有数百万次读取/写入。在个人层面上,我只是认为尽可能快地以人工方式(机器?)让它变得有趣。
-
Windows CRITICAL_SECTION 非常轻量级,除非它实际上必须阻塞。我不认为像这样忙着等待的用户线程是一个非常好的主意——你隐含地向调度程序发出信号说你有很多事情要做,而实际上情况恰恰相反。
-
在 windows 上:MemoryBarrier 被测量为需要 20-90 个周期。 InterlockedIncrement 被测量为需要 36-90 个周期。获取或释放关键部分被测量为需要 40-100 个周期。测量获得或释放互斥体大约需要 750-2500 个周期。