故事
有一个写作线程,定期从某个地方收集数据(实时的,但这在问题中并不重要)。那时有许多读者从这些数据中阅读。通常的解决方案是使用两个读取器锁和两个缓冲区,如下所示:
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
问题
在这两种方法中,如果获取其他锁操作失败,则不执行任何交换,并且写入器将覆盖其先前的数据(因为写入器是实时的,它不能等待读取器),因此在这种情况下,所有读取器都会丢失该数据帧。
这并不是什么大问题,读者是我自己的代码,而且它们很短,所以使用双缓冲区,这个问题就解决了,如果有问题,我可以让它变成三重缓冲区(或更多)。
问题是我想尽量减少延误。想象一下案例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
我已经找到的
我找到了阅读-复制-更新,据我所知,它一直在为缓冲区分配内存,并将它们释放,直到读取器用完为止,这对我来说是不可能的,原因很多。第一,线程在内核和用户空间之间共享。第二,使用RTAI,您不能在实时线程中分配内存(因为这样您的线程就会调用Linux的系统调用,从而破坏实时性!(更别提使用Linux自己的RCU实现了,因为同样的原因)
我还考虑过有一个额外的线程,在更高的频率尝试交换缓冲区,但这听起来不是一个好主意。首先,它本身需要与作者同步,其次,我有许多这样的作者--读者在不同的部分并行工作,而每个作者需要额外的一个线程似乎太多了。对于所有作者来说,一个线程在与每个作者的同步方面似乎非常复杂。
发布于 2011-11-10 01:27:50
您用于读写器锁的API是什么?你有一个定时锁吗,比如时间锁?如果是,我认为这是您问题的解决方案,如下代码所示:
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;
}
}
这里发生的事情是,无论是等待锁还是等待下一段时间,对于作者来说都无关紧要,只要它一定会在下一阶段到来之前醒来。绝对超时确保了这一点。
发布于 2011-10-18 05:02:02
这不正是三重缓冲应该解决的问题吗。因此,您有3个缓冲区,让我们称它们为write1、write2和read。写入线程在写入write1和write2之间交替进行,确保它们从不阻塞,并且始终可用最后一个完整的框架。然后,在读线程中,在某个适当的点(例如,在读取帧之前或之后),用可用的写缓冲区翻转读缓冲区。
虽然这将确保写入程序不会阻塞(仅通过翻转两个指针就可以非常快地完成缓冲区翻转,甚至可以使用CAS原子而不是锁),但仍然存在读取器必须等待其他读取器在翻转之前完成读缓冲区的问题。我认为这可以稍微解决RCU-esque与一个池的读取缓冲器,其中一个可用的一个可以翻转。
发布于 2011-10-18 07:10:29
编辑以避免动态分配
我可能会用循环队列..。我将使用内置的__sync原子操作。http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html#Atomic-Builtins
https://stackoverflow.com/questions/7805957
复制