删除原子/防护数据中的数据
Deleting data in atomic / fenced data
假设我们在 C++11 程序中使用标准 consumer/producer 模式:(来自:http://en.cppreference.com/w/cpp/atomic/memory_order)
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_consume)))
;
assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr
// yea well, it actually uses p2 for quite a while probably....
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}
现在,我想稍微更改一下生产者代码的行为。我不想简单地设置一个字符串,而是 overwrite 一个字符串。例如:
void producer()
{
std::string* p = new std::string("Hello");
ptr.store(p, std::memory_order_release);
// do some stuff
std::string* p2 = new std::string("Sorry, should have been Hello World");
ptr.store(p2, std::memory_order_release);
// **
}
这里的生产者负责字符串的生成,这意味着在我的简单世界中它也应该负责这些字符串的销毁。
因此,在标有'**'的行中,我们应该销毁字符串'p',这就是这个问题的内容。
您可能考虑的解决方案是添加(在标记行):
delete p;
但是,这会破坏程序,因为消费者可能会在我们删除它之后使用该字符串——毕竟,消费者使用的是指针。此外,这意味着生产者等待消费者,这不是必需的——我们只是希望清理旧内存。使用引用计数智能指针似乎是不可能的,因为原子只支持那么多类型。
解决此问题的最佳(最有效)方法是什么?
您可以执行 atomic exchange,这将 return 原子变量的先前值。
变量 ptr
然后有两种状态:它可以没有可用数据,在这种情况下它等于 nullptr
,或者有数据可供使用。
要消费 数据,任何消费者都可以 ptr
与 nullptr
交换 ptr
。
如果没有数据,仍然没有任何数据,消费者将不得不稍后重试(这有效地构建了一个自旋锁)。
如果有数据,消费者现在拥有所有权并负责在不再需要时删除它。
为了生产数据,生产者交换ptr
与指向生产数据的指针。
- 如果没有数据,前一个指针会等于
nullptr
,数据成功产生。
- 如果有数据,生产者实际上收回了之前生产的数据的所有权。然后它可以删除该对象,或者 - 更有效地 - 简单地在下一次生产中重新使用它。
假设我们在 C++11 程序中使用标准 consumer/producer 模式:(来自:http://en.cppreference.com/w/cpp/atomic/memory_order)
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_consume)))
;
assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr
// yea well, it actually uses p2 for quite a while probably....
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}
现在,我想稍微更改一下生产者代码的行为。我不想简单地设置一个字符串,而是 overwrite 一个字符串。例如:
void producer()
{
std::string* p = new std::string("Hello");
ptr.store(p, std::memory_order_release);
// do some stuff
std::string* p2 = new std::string("Sorry, should have been Hello World");
ptr.store(p2, std::memory_order_release);
// **
}
这里的生产者负责字符串的生成,这意味着在我的简单世界中它也应该负责这些字符串的销毁。
因此,在标有'**'的行中,我们应该销毁字符串'p',这就是这个问题的内容。
您可能考虑的解决方案是添加(在标记行):
delete p;
但是,这会破坏程序,因为消费者可能会在我们删除它之后使用该字符串——毕竟,消费者使用的是指针。此外,这意味着生产者等待消费者,这不是必需的——我们只是希望清理旧内存。使用引用计数智能指针似乎是不可能的,因为原子只支持那么多类型。
解决此问题的最佳(最有效)方法是什么?
您可以执行 atomic exchange,这将 return 原子变量的先前值。
变量 ptr
然后有两种状态:它可以没有可用数据,在这种情况下它等于 nullptr
,或者有数据可供使用。
要消费 数据,任何消费者都可以 ptr
与 nullptr
交换 ptr
。
如果没有数据,仍然没有任何数据,消费者将不得不稍后重试(这有效地构建了一个自旋锁)。
如果有数据,消费者现在拥有所有权并负责在不再需要时删除它。
为了生产数据,生产者交换ptr
与指向生产数据的指针。
- 如果没有数据,前一个指针会等于
nullptr
,数据成功产生。 - 如果有数据,生产者实际上收回了之前生产的数据的所有权。然后它可以删除该对象,或者 - 更有效地 - 简单地在下一次生产中重新使用它。