Multithreading 环形缓冲区,1个写入程序和N个读卡器

Multithreading 环形缓冲区,1个写入程序和N个读卡器,multithreading,algorithm,c++11,design-patterns,concurrency,Multithreading,Algorithm,C++11,Design Patterns,Concurrency,我需要一种环形缓冲区或一些更合适的数据结构,以备不时之需,以及在以下情况下处理环形缓冲区的算法/模式 1连续生成实时数据的写入器必须始终能够将未在读取过程中的插槽写入第一个空闲插槽,或者等待一个插槽空闲后再写入。每当写入程序完成将数据写入一个插槽时,它都可以为读卡器提交该插槽 在给定时间,可能有N个并发读卡器。在发出读取请求时,每个读卡器应始终从环形缓冲区中最后一个提交的插槽中获取最新写入的数据,但不得多次读取相同的数据。如果自上次读取后,写入程序尚未在一个插槽中写入和提交新数据,则读卡器应该等

我需要一种环形缓冲区或一些更合适的数据结构,以备不时之需,以及在以下情况下处理环形缓冲区的算法/模式

1连续生成实时数据的写入器必须始终能够将未在读取过程中的插槽写入第一个空闲插槽,或者等待一个插槽空闲后再写入。每当写入程序完成将数据写入一个插槽时,它都可以为读卡器提交该插槽

在给定时间,可能有N个并发读卡器。在发出读取请求时,每个读卡器应始终从环形缓冲区中最后一个提交的插槽中获取最新写入的数据,但不得多次读取相同的数据。如果自上次读取后,写入程序尚未在一个插槽中写入和提交新数据,则读卡器应该等待—请考虑快速读卡器

请注意,一个读卡器不能为另一个读卡器使用数据。换句话说,两个不同的读取器可以读取相同的数据。同样,一个读卡器可以从同一个插槽中读取数据两次或更多次,但仅当写入器在两个读取请求之间写入该插槽时

请注意,破坏者可能不适合我的情况,或者我只是没能让它按我想要的方式工作。干扰器的问题是,与较慢的读卡器相比,写入器可能前进得太快,以至于在读取某些插槽的过程中可能会覆盖这些插槽。在这种情况下,编写器应该能够跳过这些繁忙的插槽,直到它找到第一个空闲的插槽。一旦写入,它还必须只发布该插槽,但是中断器模式似乎没有考虑到这种情况。序列本身还有另一个问题,在我使用的中断器实现中,它是一个原子整数,因此它可能会溢出,导致一些未定义的行为


你知道吗?我很欣赏现代C++中的解决方案,如果你知道的话。

< p>我不知道这个问题的一个方块解决方案:这并不意味着没有一个!但是,如果我尝试使用c++11工具实现这一点,我会看到如下内容:

保存数据的数组 用于保存已写入元素的索引的std::stack 一个std::队列,用于保存已读取元素的索引 用于控制对堆栈的访问的互斥体。因此,读写器在弹出/推送到堆栈之前先获取互斥锁。 用于控制对队列的访问的互斥体。因此,读写器在进入/退出队列之前获取互斥锁。 读卡器条件变量,用于在堆栈为空时阻止读卡器 队列为空时写入程序要阻止的写入程序条件变量

开始将环形缓冲区的所有索引大小排入队列

写入程序获取索引并写入数组,然后将索引推送到堆栈上并通知读卡器。 读卡器从堆栈中弹出索引,完成后将其排入队列并通知编写器。 空堆栈导致读卡器等待写入器,空队列导致写入器等待读卡器。 进一步考虑一下,这个建议试图做的是将数组中的插槽视为一个资源池。读取器获取资源,即插槽的索引,并在其完成处理后返回

更新的解决方案:由于插槽数量最多限制为8个,我建议您尽可能简化解决方案。使每个插槽对其自身的状态负责:

#include <atomic>
template <typename T>
class Slot {
   atomic_uint readers ;
   int iteration ;
   T data ;
}
如果读写器以这种方式发生冲突,则两者都无法访问,都需要重试

Execution       Writer                  Reader
1               readers == 0 
2                                       readers >= 0
3               readers--
4                                       readers++
5               readers == -1 is false
6                                       readers > 0 is false!
7               readers++
8                                       readers--


Execution       Writer                  Reader
1                                       readers >= 0
2               readers == 0 
3                                       readers++
4               readers--
5                                       
6               readers == -1 is false
7                                       readers > 0 is false
8               readers++
9                                       readers--
如果您对这种方法不满意,那么可以使用互斥锁和lock/try_锁,类似于Slot类NB:目前无法通过编译器运行,因此可能存在语法错误

typedef enum LOCK_TYPE {READ, WRITE} ;
std::mutex mtx ;

bool lockSlot(LOCK_TYPE lockType)
{
    bool result = false ;
    if (mtx.try_lock()) {

        if ((lockType == READ) && (readers >= 0)) 
            readers++ ;
        if ((lockType == WRITE) && (readers == 0)) 
            readers-- ;
        result = true ;
    }
    return result ;
}

void unlockSlot (LOCK_TYPE lockType)
{
    if (mtx.lock()) {  // Wait for a lock this time

        if (lockType == READ) 
            readers-- ;
        if (lockType == WRITE)
            readers++ ;
    }
}

当/如果读卡器耗尽工作时,它可以等待一个条件变量,编写器可以使用该条件变量在新数据可用时通知所有读卡器

如果几年前我正确理解了这个问题,并且我有了一个解决方案,我也会面临同样的问题。一篇关于算法的期刊论文是可以接受的,当它发表时,我会在这里放一篇参考文献,但是现在,我可以告诉你代码

然而,首先,这是在C语言中实时完成的,目前可能在用户空间中使用POSIX后端,或者在用户或内核空间中使用RTAI后端

第二,我的一个要求是,编写器永远不会因为用户而被阻止。也就是说,如果写入程序找不到可用缓冲区,它将覆盖当前数据帧。我也解决了这个问题,我的测试表明,通过读者部分的小协作,这是完全可以避免的

还请注意,这个想法类似于周期性数据缓冲区1和周期性异步缓冲区2,尽管我是自己提出的。他们 使用原子操作,而我使用读写器锁。此外,它们没有像我上面提到的那样为交换跳过提供解决方案

最后,作者和读者不仅仅是周期性的。编写器可以是周期性的,也可以是零星的,每个请求都可以运行,而读者可以是周期性的,零星的,也可以是尽力而为的

您可以找到writer实现和reader实现

为完整起见,我将在多缓冲区模式下使用周期写入器和读取器的基本算法粘贴注释:

Writer:

write_lock(cur)
loop {
         if last period no swap
                 try swap again
         write
         try while period left
                 write_lock(next)
                 write_unlock(cur)
                 cur = next
         wait period
}
unlock(cur)

Reader:

loop {
        if last is new
                b = last
        else
                b = cur
        read_lock(b)
        read
        read_unlock(b)
        wait period
}
写入时,写入程序还为缓冲区中的数据添加时间戳。这有助于读者了解数据是新的还是旧的

关于writer中的缓冲区交换的一点是,它尝试交换缓冲区,直到成功或接近其周期结束。这是通过尝试锁定所有其他缓冲区来完成的,以查看在第一次成功/周期结束时停止的可用缓冲区。当然,无忙循环。一旦完成此操作,它还通过共享内存(可能与包含数据缓冲区的共享内存相同)告知何时需要再次交换缓冲区,以及最后哪个缓冲区包含最新数据,以及写入程序当前控制cur的哪个缓冲区

读者可以选择。如果最后一个包含新数据,则读取\锁定它并继续读取。如果没有,或者如果读取器可以估计它无法在下一次缓冲区交换时执行读取,则它将等待cur解锁。一旦写入程序解锁,实时调度器就会尽快唤醒读卡器。如果系统未处于重负载下,这意味着几乎是即时的

为了让读者知道它是否能够及时执行读取,它会跟踪其在周期中的平均执行时间,并使用该时间检查当前\u时间+我的\u执行\u时间>下一次\u预期的\u交换。在这种情况下,它无法及时执行读取,因此它将在cur上等待,而cur很快就会解锁

1 HIC:伺服回路层次的操作系统,克拉克,D.,机器人与自动化,1989年。1989年IEEE国际会议记录

2 HARTIK:机器人应用的实时内核,Buttazzo,G.C.,实时系统研讨会,1993年,会议记录


注意:虽然此库主要用于机器人皮肤数据处理,但您基本上可以将其用于具有上述属性的任何常规writer-reader通信。此外,请注意,我指向的存储库的主分支包含一个旧版本。我所指的分支skinware-2有一个更好、功能更丰富的实现。

它不能像我所希望的那样工作:读取器从堆栈中弹出索引,完成后将其放入队列并通知编写器。许多读者可以共享同一个索引。正如我所说,如果一个新的索引还没有被推到堆栈中,那么一个读者不能通过弹出一个索引来窃取后续读者的索引。@Martin-我真的不确定你这是什么意思。您有固定数量的插槽,而编写器必须等待空闲插槽,那么插槽如何变为空闲插槽?是否所有读卡器都需要读取插槽中的数据,或者是否有其他标准?是否允许读卡器同时或顺序或同时读取数据?我认为你需要更详细地解释你需要什么。一个给定的读者不能阅读相同的数据两次。但是给定两个读卡器A和B,假设A先读一些输入。使用您的解决方案,它将从读卡器队列中弹出。然后B请求数据,但无法读取A刚刚读取的内容。你需要一些东西来跟踪读者他们已经读了什么?@Rerito:你明白我的意思了。这就是为什么这个问题比看起来更难的原因。我想知道为什么对于这个问题,显然没有现成的解决方案,1个writer实时N个readers总是获取最新的可能数据,并且唯一的限制是writer不能覆盖正在读取的内容。复制数据进行独立读取是不可能的,因为复制本身就是这样expansive@Jackson除了Rerito所说的,是的,正如我所说,阅读可能是同时进行的。谢谢,我需要一些时间来分析你的解决方案。我会尽快告诉你的。