想要一个高效的方法在C++中交换两个指针?

3

在一个多线程的C++应用程序中,我有两个指针指向一个大的图形对象。 我尝试每5分钟交换它们。哪种方式是安全的并具有最佳性能? 使用C++11的atomic<>是一个不错的选择。


1
我会想象将它们保存在std::atomic<>中并调用compare_exchange函数,例如:http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange - Nim
15
你每五分钟进行一次交换,却想要“最佳性能”?! - David Schwartz
1
@DavidSchwartz,交换可能不是问题,但每次读取指针时检查是否正在交换可能会... - Lanting
3
一种方法是声明 std::atomic_int the_flipper; 并将其从 0 翻转到 1,然后再翻转回来:代码可以获取 ptr[the_flipper]ptr[the_flipper^1] - 当 the_flipper 更改时会解析为不同的指针。然而,我很难想象什么情况下指针短暂相同会有影响(由于竞争条件,无论如何进行原子交换,任何同时读取两个指针的人都可能看到相同的结果),因此像 axiac 建议的那样使用两个 std::atomic_uintptr_t 和一个临时变量可能完全可以胜任。 - Tony Delroy
1
@Nim 确实,你说得对,问题的一部分需要安全地交换指针。我所提到的是性能问题。 - axiac
显示剩余7条评论
1个回答

6

交换需要原子操作吗?也就是说,在交换时,您是否需要保证另一个线程不能观察到它们在交换时具有相同的值?

如果您不需要保证交换是原子的,则只需使用两个std::atomic<T *>对象,并使用临时变量进行交换:

T* tmp = p1;
p1 = p2.load();
p2 = tmp;

如果您需要交换是原子性的,则需要将它们都存储在同一个结构体中,以便可以通过单个步骤进行更新,例如,要在标准C++中执行这样的操作。
struct TwoPointers { T* p1; T* p2; }
std::atomic<TwoPointers> p;

那么你可以这样交换它们:
auto old = p.load();
p = { old.p2, old.p1 };

这里使用双字节原子操作,可能会在后台实现互斥锁,因此通过临时变量进行简单赋值可能会更快,但我怀疑每五分钟一次不会有什么区别。
上述两个版本都假定只有一个线程会尝试同时交换它们,因为虽然旧值的加载是原子的,新值的写入也是原子的,但在这两个步骤之间有一个窗口,在此期间另一个线程可以改变指针值。如果这是个问题,请改用以下的std::atomic :
auto old = p.load();
while (!p.compare_exchange_strong(old, { old.p2, old.p1 }))
{ };

在循环中使用compare_exchange_strong比单个原子存储略慢,但如果每五分钟只发生一次,那么你不会注意到差异。
这种TwoPointers解决方案也意味着两个指针共享一个缓存行,但如果除了每五分钟的交换之外它们是只读的,那就不是问题。

1
万一需要,这里有一个替代的无等待解决方案:https://stackoverflow.com/a/54561350/412080 - Maxim Egorushkin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接