序列锁中的原子线程围栏要求 我写了一个简单的,通过C++和DOC的ActudioTyRead篱笆阅读,感觉我需要一个篱笆。然而,我写这本书时没有任何限制,而且我有一个相当严格的测试,似乎没有它也能通过

序列锁中的原子线程围栏要求 我写了一个简单的,通过C++和DOC的ActudioTyRead篱笆阅读,感觉我需要一个篱笆。然而,我写这本书时没有任何限制,而且我有一个相当严格的测试,似乎没有它也能通过,c++,thread-safety,atomic,lock-free,memory-barriers,C++,Thread Safety,Atomic,Lock Free,Memory Barriers,基本的序列锁概念是这样的。您有一个编写器,可以随时更新某个共享内存。它将递增的序列号与数据关联。在开始写入之前,它会递增一次,而在完成写入之后,它会再次递增 在读卡器端,在开始查看数据之前,它会等待序列号变为偶数(如果是奇数,则意味着我们正在进行中间写入)。然后,它跟踪在开始时看到的序列号,在本地复制数据,然后在完成后再次检查序列号。如果它们匹配,则数据是干净且可用的。如果它们不匹配,那么我们很容易受到部分读取的影响,然后重试 sequencer编号存储在std::atomic中。写入程序在块的

基本的序列锁概念是这样的。您有一个编写器,可以随时更新某个共享内存。它将递增的序列号与数据关联。在开始写入之前,它会递增一次,而在完成写入之后,它会再次递增

在读卡器端,在开始查看数据之前,它会等待序列号变为偶数(如果是奇数,则意味着我们正在进行中间写入)。然后,它跟踪在开始时看到的序列号,在本地复制数据,然后在完成后再次检查序列号。如果它们匹配,则数据是干净且可用的。如果它们不匹配,那么我们很容易受到部分读取的影响,然后重试

sequencer编号存储在std::atomic中。写入程序在块的开头和结尾使用存储(std::memory\u order\u release)对其进行更新。 读卡器在读取的开始和结束时使用加载(std::memory\u order\u acquire)读取序列号

要从writer->reader复制的有问题的数据完全独立于原子seqno,这就是为什么我觉得我可能需要一个围栏来同步无依赖性的数据。。。但不管我怎么说,它似乎都能起作用。 我在一台多插槽/多核Intel Xeon机器上运行,也许它专门在这个CPU上工作

代码片段:

void SequenceLock::writeLock()
{
    m_seqno.store( m_seqno.load( std::memory_order_relaxed ) + 1, std::memory_order_release );
}

void SequenceLock::writeUnlock()
{
    m_seqno.store( m_seqno.load( std::memory_order_relaxed ) + 1, std::memory_order_release );
}

uint64_t SequenceLock::enterRead() const
{
    uint64_t seqno;
    while( ( seqno = m_seqno.load( std::memory_order_acquire ) ) & 1 ) {} //no yield, expect readers to be pinned
    return seqno;
}

bool SequenceLock::validateRead( uint64_t prevSeqno ) const
{
    return m_seqno.load( std::memory_order_acquire ) == prevSeqno;
}


//sample writer
lock.writeLock()
//update some shared data
lock.writeUnlock()

//sample reader
uint64_t seqno;
do {
    seqno = lock.enterRead()
    //copy shared data locally
} while( !lock.validateRead( seqno ) )

编辑:更奇怪的是,我把上面访问的所有内存都改成了“内存顺序”\u released,它似乎仍然工作。。。奇怪的是,我没有看过您的代码,但是默认情况下,
x64
ISA的强大性让人觉得可笑,这一点毫无价值。CPU可以并且将覆盖多线程代码中的许多漏洞。这并不意味着你的代码是正确的,它只是意味着它可能会工作。从更多信息中可以看出,是的,我发现C++的内存模型/规则对于实现SeqLock有些不方便。e、 g.您希望让编译器使用高效的大型存储来复制数据,而不必强制将数据分成小块,这些小块可以通过
std::atomic
进行无锁操作。这在技术上会造成数据竞争,但在任何实际实现中仍然是安全的。看看我的尝试。是的,我发现我需要篱笆。@ MikeVine:注意C++内存模型仍然很弱。因此,在x86上测试时,在许多情况下,唯一的危险是编译时重新排序,而StoreLoad重新排序/存储缓冲区不是问题。就像这里一个线程只存储,另一个线程只读取。(x86 TSO是顺序一致性+具有存储转发的存储缓冲区。)@PeterCordes如果您了解您的CPU,您可以使用您知道在数据竞争中是原子的或安全的易失性操作(所有CPU在数据竞争中都是安全的)。在C/C++
中,volatile
精确地给出了CPU的语义。现在,可以使用编译器栅栏来“附加”易失性操作,以便它们按照非易失性操作顺序完成,这些操作以前不可移植:像STDIO这样的挥发物操作是按WRT顺序进行的,但WRT是非易失性操作。@好奇人:C++中您不能从
volatile结构foo
struct foo tmp
执行结构赋值。因此,如果任意类型是易失性的,您不能要求编译器为您高效地复制任意类型。在C语言中,你可以。我没有看过你的代码,但是默认情况下,
x64
ISA强大得可笑是毫无价值的。CPU可以并且将覆盖多线程代码中的许多漏洞。这并不意味着你的代码是正确的,它只是意味着它可能会工作。从更多信息中可以看出,是的,我发现C++的内存模型/规则对于实现SeqLock有些不方便。e、 g.您希望让编译器使用高效的大型存储来复制数据,而不必强制将数据分成小块,这些小块可以通过
std::atomic
进行无锁操作。这在技术上会造成数据竞争,但在任何实际实现中仍然是安全的。看看我的尝试。是的,我发现我需要篱笆。@ MikeVine:注意C++内存模型仍然很弱。因此,在x86上测试时,在许多情况下,唯一的危险是编译时重新排序,而StoreLoad重新排序/存储缓冲区不是问题。就像这里一个线程只存储,另一个线程只读取。(x86 TSO是顺序一致性+具有存储转发的存储缓冲区。)@PeterCordes如果您了解您的CPU,您可以使用您知道在数据竞争中是原子的或安全的易失性操作(所有CPU在数据竞争中都是安全的)。在C/C++
中,volatile
精确地给出了CPU的语义。现在,可以使用编译器栅栏来“附加”易失性操作,以便它们按照非易失性操作顺序完成,这些操作以前不可移植:像STDIO这样的挥发物操作是按WRT顺序进行的,但WRT是非易失性操作。@好奇人:C++中您不能从
volatile结构foo
struct foo tmp
执行结构赋值。因此,如果任意类型是易失性的,您不能要求编译器为您高效地复制任意类型。用C你可以。