我有一个永不锁定或分配内存的使用者线程,以及一个可以锁定和分配内存的生产者线程。 我想实现一个两位循环缓冲区,以便能够从生产者向消费者线程提供数据,并且绑定的是,只要没有新数据可供使用,消费者只需重用已经可用的数据。
这就是我现在想出的:
bool newDataAvailable = false;
bool bufferEmpty = true;
foo* currentData = new foo();
foo* newData = new foo();
void consumer() {
while(true) {
currentData->doSomething();
if(newDataAvailable) {
foo* tmp = currentData;
// Objects are swapped so the old one can be reused without additional allocations
currentData = newData;
newData = tmp;
newDataAvailable = false;
bufferEmpty = true;
}
}
}
void producer() {
while(true) {
while(!bufferEmpty) { wait(); }
newData->init();
bufferEmpty = false;
newDataAvailable = true;
}
}
这种幼稚的实现可以吗?我知道读取和写入变量可以是非原子的,所以我应该使用原子存储,但这些可能会导致锁定。这里是否需要使用原子存储? 另外,我想消除生产者中的主动等待,我认为我可以使用std::condition_variable
,但它们需要使用互斥体,我负担不起。
编写共享变量而不使用互斥锁的多线程代码很难正确。 请参阅无锁编程简介、无锁缓冲区。
如果您绝对必须避免使用互斥锁,那么我强烈建议您使用预制的无锁队列,例如 Boost.lockfree 或 MPMCQueue 作为轻量级的非助推替代方案。
我知道读取和写入变量可以是非原子的,所以我应该使用原子存储,但这些可能会导致锁定。
对于所有基元类型(不超过 CPU 的本机大小),std::atomic
通常是无锁的(不使用互斥锁)。 您可以通过调用std::atomic<T>::is_lock_free
来检查std::atomic
是否会对给定类型使用互斥锁
这里是否需要使用原子存储?
是的,绝对。您要么需要使用互斥体,要么需要使用原子组学。
另外,我想消除生产者中的活动等待,我认为我可以使用std::condition_variable
当您无法使用互斥锁时,您唯一的选择就是使用旋转锁。 如果上下文中允许这样做,则可以在旋转锁中使用std::this_thread::yield()
来减少 CPU 负载。(但是互斥锁可能会更快)
编辑: 只有 2 个原子的潜在解决方案是:
std::atomic<foo*> currentData = new foo();
std::atomic<foo*> newData = new foo();
void consumer() {
foo* activeData = currentData;
while (true) {
activeData->doSomething();
foo* newItem = currentData;
if (newItem != activeData) {
newData = activeData;
activeData = newItem;
}
}
}
void producer() {
while (true) {
foo* reusedData = newData;
if (!reusedData)
continue;
newData = nullptr;
reusedData->init();
currentData = reusedData;
}
}