c - 在单写多读线程中交换缓冲区

标签 c linux algorithm readerwriterlock double-buffering

故事

有一个编写器线程,定期从某个地方收集数据(实时,但这在问题中并不重要)。然后有许多读者从这些数据中阅读。通常的解决方案是使用两个读写器锁和两个缓冲区,如下所示:

Writer (case 1):
acquire lock 0                        
loop
    write to current buffer
    acquire other lock
    free this lock
    swap buffers
    wait for next period

或者

Writer (case 2):
acquire lock 0                        
loop
    acquire other lock
    free this lock
    swap buffers
    write to current buffer
    wait for next period

问题

在这两种方法中,如果acquire other lock 操作失败,则不会进行交换,writer 将覆盖其之前的数据(因为 writer 是实时的,它不能等待 readers)所以在这种情况下,所有读者都会丢失该帧数据。

不过这没什么大不了的,读者是我自己的代码而且它们很短,所以有了双缓冲区,这个问题就解决了,如果有问题我可以把它变成三缓冲区(或更多)。

问题是我想尽量减少延迟。想象一下情况 1:

writer writes to buffer0                reader is reading buffer1
writer can't acquire lock1              because reader is still reading buffer1
|                                       |
|                                       reader finishes reading,
| (writer waiting for next period)      <- **this point**
|
|
writer wakes up, and again writes to buffer0

在**此时**,理论上其他读者可以读取 buffer0 的数据,只要作者可以在读者完成后进行交换而不是等待下一个周期。这种情况下发生的情况是,仅仅因为一个读取器晚了一点,所有读取器都错过了一帧数据,而这个问题是完全可以避免的。

情况2类似:

writer writes to buffer0                reader is idle
|                                       |
|                                       reader finishes reading,
| (writer waiting for next period)
|
|                                       reader starts reading buffer1
writer wakes up                         |
it can't acquire lock1                  because reader is still reading buffer1
overwrites buffer0

我尝试混合解决方案,因此作者尝试在写入后立即交换缓冲区,如果不可能,则在下一个周期醒来后立即交换缓冲区。所以像这样:

Writer (case 3):
acquire lock 0                        
loop
    if last buffer swap failed
        acquire other lock
        free this lock
        swap buffers
    write to current buffer
    acquire other lock
    free this lock
    swap buffers
    wait for next period

现在延迟的问题仍然存在:

writer writes to buffer0                reader is reading buffer1
writer can't acquire lock1              because reader is still reading buffer1
|                                       |
|                                       reader finishes reading,
| (writer waiting for next period)      <- **this point**
|
|
writer wakes up
swaps buffers
writes to buffer1

再次在**此时**,所有读者都可以开始阅读 buffer0,这是在写入 buffer0 之后的短暂延迟,但他们必须等下期写手。

问题

问题是,我该如何处理?如果我想让编写器在所需的时间段精确执行,它需要使用 RTAI 函数等待时间段,我不能这样做

Writer (case 4):
acquire lock 0                        
loop
    write to current buffer
    loop a few times or until the buffer has been swapped
        sleep a little
        acquire other lock
        free this lock
        swap buffers
    wait for next period

这会引入抖动。因为“几次”可能恰好变得比“等待下一个时期”更长,所以作者可能会错过其时期的开始。

为了更清楚,这是我想要发生的事情:

writer writes to buffer0                reader is reading buffer1
|                                       |
|                                       reader finishes reading,
| (writer waiting for next period)      As soon as all readers finish reading,
|                                         the buffer is swapped
|                                       readers start reading buffer0
writer wakes up                         |
writes to buffer1

我已经发现了什么

我找到了 read-copy-update据我所知,它一直为缓冲区分配内存并释放它们,直到读者用完它们为止,这对我来说是不可能的,原因有很多。第一,线程在内核空间和用户空间之间共享。其次,使用 RTAI,您不能在实时线程中分配内存(因为那样您的线程将调用 Linux 的系统调用,从而破坏实时性!(更不用说使用 Linux 自己的 RCU 实现是无用的,因为出于同样的原因)

我还考虑过有一个额外的线程,以更高的频率尝试交换缓冲区,但这听起来不是一个好主意。首先,它本身需要与作者同步,其次,我有许多这样的作者-读者在不同的部分并行工作,每个作者一个额外的线程似乎太多了。对于与每个作者的同步,所有作者的一个线程似乎非常复杂。

最佳答案

您使用什么 API 来实现读写锁?你有定时锁吗,比如pthread_rwlock_timedwrlock ?如果是,我认为它是您问题的解决方案,如以下代码所示:

void *buf[2];

void
writer ()
{
  int lock = 0, next = 1;

  write_lock (lock);
  while (1)
    {
      abs_time tm = now() + period;

      fill (buf [lock]);
      if (timed_write_lock (next, tm))
        {
          unlock (lock);
          lock = next;
          next = (next + 1) & 1;
        }
      wait_period (tm);
    }
}


void
reader ()
{
  int lock = 0;
  while (1)
    {
      reade_lock (lock);
      process (buf [lock]);
      unlock (lock);
      lock = (lock + 1) & 1;
    }
}

这里发生的事情是,对于 writer 来说,它是等待锁还是等待下一个周期并不重要,只要它肯定会在下一个周期到来之前醒来。绝对超时确保了这一点。

关于c - 在单写多读线程中交换缓冲区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7805957/

相关文章:

c - 在 B 树中查找第 k 个键的算法?

c - 关于词法分析的问题

c - VSCode C/C++ Intellisense 问题与 netinet/ip.h

c - 如何创建一个新线程

android - "No such file or directory"试图在 Android 设备上执行 linux 二进制文件

linux - 如何确定由于新软件包安装而导致的文件系统变化

c - 用显式转换替换隐式转换有任何副作用吗?

c - 这在 C 语言中的 while 循环是什么意思?

linux - 在 Linux 中向屏幕发送命令

algorithm - asymptotic-complexity - 计算原始操作的步骤