我对C++非常陌生,我想专注于编写性能良好的多线程代码,因为我将尝试移植我们公司的内部GUI框架,该框架目前是用C#实现的。因此,我想获得一些关于代码风格和性能的建议,这些建议对我所写的读者-作者自旋锁是有利的:
namespace UI
{
namespace Threading
{
/**
* \brief ReaderWriterSpinLock which favors writers
*/
class ReaderWriterSpinLock
{
public:
ReaderWriterSpinLock() = default;
~ReaderWriterSpinLock() = default;
ReaderWriterSpinLock(ReaderWriterSpinLock&) = delete;
ReaderWriterSpinLock(ReaderWriterSpinLock&&) = delete;
ReaderWriterSpinLock& operator =(ReaderWriterSpinLock&) = delete;
ReaderWriterSpinLock& operator =(ReaderWriterSpinLock&&) = delete;
void AcquireReaderLock()
{
int computedValue, initialValue;
do
{
int count{0};
while (m_waitingWriters != 0 || (initialValue = m_lockCount) < 0)
{
if (++count % MAX_SPIN_COUNT == 0)
std::this_thread::yield();
}
computedValue = initialValue + 1;
}
while (!m_lockCount.compare_exchange_strong(initialValue, computedValue));
}
void ReleaseReaderLock()
{
assert(m_lockCount > 0);
--m_lockCount;
}
void AcquireWriterLock()
{
int computedValue, initialValue;
++m_waitingWriters;
do
{
int count{0};
while (m_lockCount != 0)
{
if (++count % MAX_SPIN_COUNT == 0)
std::this_thread::yield();
}
initialValue = 0;
computedValue = -1;
}
while (!m_lockCount.compare_exchange_strong(initialValue, computedValue));
--m_waitingWriters;
}
void ReleaseWriterLock()
{
assert(m_lockCount == -1);
++m_lockCount;
}
private:
static constexpr int MAX_SPIN_COUNT = 1000;
std::atomic m_lockCount{0};
std::atomic m_waitingWriters{0};
};
}
}说明:负m_lockCount表示活动写入器(因为一次只能有一个写入器,最小值为-1),正计数表示活动读取器。当查询读取器锁时,它会旋转(并生成每个MAX_SPIN_COUNT自旋),直到没有等待的写入器(因为它应该支持这些作者),并且没有活动的写入器(m_lockCount >= 0),然后它尝试更新锁计数并在失败时重复该进程。释放锁时,无需进一步检查,锁计数就会减少。写入器锁的方法与暂时递增m_waitingWriters而不是在获取方法中检查它们的方法非常相似。
我知道您通常应该依赖库函数,但在我的测试中,获得写入器锁的速度是boost::shared_mutex的10倍,对于D5(四个阅读器试图在一个循环中获取读取器锁)速度是boost::shared_lock和boost::unique_lock的两倍。所以这是值得的。
同样,在我的应用程序中,线程计数等于处理器计数,因此很少需要产生结果,而自旋锁只用于只需要几个周期的方法。
我还想显式地删除移动和复制构造函数,因为这对这样的类是有意义的(尽管它们因为原子字段而被隐式删除)。这是个好主意吗
发布于 2019-02-19 19:32:33
无锁代码很少容易理解。
我不敢苟同。它通常只是隐藏了复杂的情况。
自旋锁通常不是一个好主意。您应该确信,陷入自旋锁中的线程将快速逃脱(否则您将熔化您的处理器)。
但是您可以通过使用std::this_thread::yield()来缓解这个问题,所以这不是一个很大的问题。所以这不是我所说的经典的自旋锁。更多的是随着屈服而旋转。
我看到的唯一问题是,对于某个特定线程,您不能保证锁的逸出。
一些特殊的不幸线程在试图获得锁时可能会被捕获,而其他线程则会通过并继续获取锁,从而迫使不幸的线程继续尝试退出获取。
因此,您的自旋锁有可能导致某些资源饥饿(或者最坏的情况是编写代码序列)。
这通常是通过维持秩序来实现的。
您需要将解释放入代码中(作为注释)。把它作为这个网站的一个解释是很好的,并允许我理解代码。但如果没有它,代码就无法解密。
您已经设计了需要匹配方法调用的代码(这是错误的做法)。此外,您还没有为这个类显示RAII储物柜,所以我们必须假定它的使用并不是例外的安全。
您的所有四种方法:
void AcquireReaderLock()
void ReleaseReaderLock()
void AcquireWriterLock()
void ReleaseWriterLock()应该是私人成员。您应该只允许通过RAII锁保护来访问这些方法。以std::lock为例。
轻微的最佳做法违规行为:
// One declaration per line
int computedValue, initialValue;
// Why not initialize these on declaration.
initialValue = 0;
computedValue = -1;
// There is no need to ever set them again.https://codereview.stackexchange.com/questions/213823
复制相似问题