我有一些工作线程定期(大约1kHz)执行时间关键型处理。每一个周期,工人们都会被叫醒做家务
应该
(平均)在下一个周期开始之前完成。它们对同一个对象进行操作,主线程可以偶尔修改该对象。
为了防止竞争,但允许在下一个周期之前对对象进行修改,我使用了旋转锁和原子计数器来记录有多少线程仍在工作:
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
:具有此内存顺序的加载操作执行
获取操作
在受影响的内存位置:在加载之前,不能对当前线程中的内存访问进行重新排序。这确保了释放相同原子变量的其他线程中的所有写入在当前线程中可见。
memory_order_release
:具有此内存顺序的存储操作执行
释放操作
:当前线程中的任何内存访问都不能在此存储之后重新排序。这确保了当前线程中的所有写入在获取相同原子变量的其他线程中可见,并且将依赖关系写入到原子变量的写入在使用相同原子的其他线程中将可见。
正如我所理解的那样,这足以在线程之间同步受保护的访问,以提供互斥行为,而不会对内存排序过于保守。
我想知道的是,内存排序是否可以进一步放宽,因为这种模式的副作用是我正在使用自旋锁互斥体来同步另一个原子变量。
呼叫
++workers_busy
,
--workers_busy
和
workers_busy.load()
所有当前都具有默认的存储顺序,
memory_order_seq_cst
鉴于此原子唯一有趣的用途是解除阻塞
Modify()
具有
--工人_公共汽车
(这是
不
由自旋锁互斥体同步),是否可以使用相同的获取释放内存顺序,使用“松弛”增量来处理这个变量?
即
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 ); // <--
}
这是正确的吗?有可能进一步放松这些记忆顺序吗?这有关系吗?