gpt4 book ai didi

c++ - 具有两个原子的自旋锁的最小限制内存排序

转载 作者:可可西里 更新时间:2023-11-01 18:39:56 27 4
gpt4 key购买 nike

我有一些工作线程定期(大约 1 kHz)执行时间关键处理。每个周期, worker 们都会被叫醒去做一件家务,每件家务应该(平均而言)在下一个周期开始之前完成。它们对同一个对象进行操作,该对象偶尔会被主线程修改。

为了防止竞争,但允许在下一个周期之前修改对象,我使用了自旋锁和原子计数器来记录有多少线程仍在工作:

class Foo {
public:
void Modify();
void DoWork( SomeContext& );
private:
std::atomic_flag locked = ATOMIC_FLAG_INIT;
std::atomic<int> workers_busy = 0;
};

void Foo::Modify()
{
while( locked.test_and_set( std::memory_order_acquire ) ) ; // spin
while( workers_busy.load() != 0 ) ; // spin

// Modifications happen here ....

locked.clear( std::memory_order_release );
}

void Foo::DoWork( SomeContext& )
{
while( locked.test_and_set( std::memory_order_acquire ) ) ; // spin
++workers_busy;
locked.clear( std::memory_order_release );

// Processing happens here ....

--workers_busy;
}

这允许所有剩余的工作立即完成,前提是至少有一个线程已经开始,并且在另一个工作人员可以开始下一个周期的工作之前总是会阻塞。

atomic_flag 通过“获取”和“释放”内存顺序进行访问,这似乎是使用 C++11 实现自旋锁的公认方式。根据documentation at cppreference.com :

memory_order_acquire : A load operation with this memory order performs the acquire operation on the affected memory location: no memory accesses in the current thread can be reordered before this load. This ensures that all writes in other threads that release the same atomic variable are visible in the current thread.

memory_order_release : A store operation with this memory order performs the release operation: no memory accesses in the current thread can be reordered after this store. This ensures that all writes in the current thread are visible in other threads that acquire the same atomic variable and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic.

据我了解,这足以跨线程同步 protected 访问以提供互斥行为,而不会过于保守内存顺序。

我想知道的是内存排序是否可以进一步放宽,因为这种模式的副作用是我正在使用自旋锁互斥体来同步另一个原子变量。

++workers_busy--workers_busyworkers_busy.load() 的调用目前都具有默认的内存顺序,memory_order_seq_cst。考虑到此原子唯一有趣的用途是使用 --workers_busy 解除阻塞 Modify()(它由自旋锁同步mutex),是否可以使用“松弛”增量将相同的获取 - 释放内存顺序用于此变量?

void Foo::Modify()
{
while( locked.test_and_set( std::memory_order_acquire ) ) ;
while( workers_busy.load( std::memory_order_acquire ) != 0 ) ; // <--
// ....
locked.clear( std::memory_order_release );
}

void Foo::DoWork( SomeContext& )
{
while( locked.test_and_set( std::memory_order_acquire ) ) ;
workers_busy.fetch_add( 1, std::memory_order_relaxed ); // <--
locked.clear( std::memory_order_release );
// ....
workers_busy.fetch_sub( 1, std::memory_order_release ); // <--
}

这是正确的吗?是否有可能进一步放宽这些内存排序中的任何一个?这有关系吗?

最佳答案

Since you say you're targeting x86 only , 你是 guaranteed strongly-ordered memory anyway ;避免 memory_order_seq_cst 很有用(它会触发昂贵且不必要的内存栅栏),但除此之外,大多数其他操作不会强加任何特殊开销,因此除了允许之外,您不会从额外的放松中获得任何好处可能不正确的编译器指令重新排序。这应该是安全的,并且不会比任何其他使用 C++11 原子的解决方案慢:

void Foo::Modify()
{
while( locked.test_and_set( std::memory_order_acquire ) ) ;
while( workers_busy.load( std::memory_order_acquire ) != 0 ) ; // acq to see decrements
// ....
locked.clear( std::memory_order_release );
}

void Foo::DoWork( SomeContext& )
{
while(locked.test_and_set(std::memory_order_acquire)) ;
workers_busy.fetch_add(1, std::memory_order_relaxed); // Lock provides acq and rel free
locked.clear(std::memory_order_release);
// ....
workers_busy.fetch_sub(1, std::memory_order_acq_rel); // No lock wrapping; acq_rel
}

最坏的情况是,在 x86 上,这会强加一些编译器顺序约束;它不应该引入额外的围栏或不需要锁定的锁定指令。

关于c++ - 具有两个原子的自旋锁的最小限制内存排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35153905/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com