【问题标题】:Circular-/Ring-Buffer with blocking read and non-blocking write?具有阻塞读取和非阻塞写入的循环/环形缓冲区?
【发布时间】:2013-02-05 09:09:44
【问题描述】:

我正在用户空间中搜索 C 语言中的环形缓冲区实现,因此我可以在我的库中使用它。

因为我需要一个环形缓冲区

  • 非阻塞写入(=覆盖最旧的数据)
  • 如果为空则阻止读取

我搜索了一段时间,并记得我曾使用 wait_event_interruptible 和 wake_up_interruptible 在内核模式下执行类似的操作。

但是在用户空间中使用了什么,所以我也许可以结合该方法搜索环形缓冲区?我不想重新发明轮子 - 周围有很多环形缓冲区解决方案。

提前致谢并致以诚挚的问候!

编辑:

似乎pthread_cond_wait 可能等同于wait_event_interruptible。

【问题讨论】:

  • 你是在单个进程中读/写环形缓冲区,还是从多个进程中读/写?另外,什么操作系统,或者它应该是可移植的?
  • 我有一个共享库,想从主线程写入缓冲区并在另一个 (p) 线程中读取(共享库写入主线程中的文件,并且应该将数据也传递给第二个线程进行进一步处理)。操作系统是 Android 设备。
  • @MartinM。这是我也需要的确切解决方案!!!
  • 这看起来像你需要的:stackoverflow.com/questions/4405721/…
  • 你想如何处理缓冲区溢出?作家块?作家盲目地覆盖,也覆盖数据阅读器只是阅读?程序中止了?

标签: c blocking nonblocking circular-buffer


【解决方案1】:

用一些代码添加另一个答案,这与我的另一个答案中的 pseudocde 不是 1:1 匹配。将此标记为 wiki 答案,以防有人想要添加 cmets 或进行其他改进。 C phtread mutex+条件变量实现非常简单的ringbuffer:

#include <stdio.h>
#include <pthread.h>

#define RINGBUFFER_SIZE (5)
int ringbuffer[RINGBUFFER_SIZE];
unsigned reader_unread = 0;
unsigned writer_next = 0;
pthread_mutex_t ringbuffer_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t ringbuffer_written_cond = PTHREAD_COND_INITIALIZER;

void process_code(int ch) {
    int counter;
    printf("Processing code %d", ch);
    for(counter=5; counter>0; --counter) {
        putchar('.');
        fflush(stdout);
        sleep(1);
    }
    printf("done.\n");

}

void *reader() {
    pthread_mutex_lock(&ringbuffer_mutex);
    for(;;) {
        if (reader_unread == 0) {
            pthread_cond_wait(&ringbuffer_written_cond, &ringbuffer_mutex);
        }
        if (reader_unread > 0) {

            int ch;
            int pos = writer_next - reader_unread;
            if (pos < 0) pos += RINGBUFFER_SIZE;
            ch = ringbuffer[pos];
            --reader_unread;

            if (ch == EOF) break;

            pthread_mutex_unlock(&ringbuffer_mutex);
            process_code(ch);
            pthread_mutex_lock(&ringbuffer_mutex);
        }
    }
    pthread_mutex_unlock(&ringbuffer_mutex);

    puts("READER THREAD GOT EOF");
    return NULL;
}

void *writer() {
    int ch;
    do {
        int overflow = 0;
        ch = getchar();

        pthread_mutex_lock(&ringbuffer_mutex);

        ringbuffer[writer_next] = ch;

        ++writer_next;
        if (writer_next == RINGBUFFER_SIZE) writer_next = 0;

        if (reader_unread < RINGBUFFER_SIZE) ++reader_unread;
        else overflow = 1;

        pthread_cond_signal(&ringbuffer_written_cond);
        pthread_mutex_unlock(&ringbuffer_mutex);

        if (overflow) puts("WARNING: OVERFLOW!");

    } while(ch != EOF);

    puts("WRITER THREAD GOT EOF");
    return NULL;
}

int main(void)
{
    pthread_t reader_thread, writer_thread;

    puts("Starting threads. Type text and press enter, or type ctrl-d at empty line to quit.");
    pthread_create(&reader_thread, NULL, reader, NULL);
    pthread_create(&writer_thread, NULL, writer, NULL);

    pthread_join(writer_thread, NULL);
    pthread_join(reader_thread, NULL);

    return 0;
}

【讨论】:

  • 您好,我今天找到了一个可行的解决方案,我会在 1.5 周后发布。我现在正在度假 :) 但您的代码似乎更容易一些,而且可能更好。
  • 我现在正在使用这个库:code.google.com/p/ring-buff。这是一个阻塞解决方案,但我想我可以修改它,例如超时或类似的东西。非常感谢您的帮助!
  • 我现在已经删除了“ring-buff”库,我现在正在使用你的代码。太完美了,再次感谢!
【解决方案2】:

对于 pthreads,标准方法是使用a mutex 并为waiting in one thread 使用条件变量直到woken up by another。

伪代码,写入器会短暂阻塞,但不会无限期阻塞,缓冲区溢出是通过丢弃未读数据来处理的:

作家写:

acquire new data to write
lock mutex
get current writing position in buffer
compare to current reading position and check for overflow
    in case of overflow, update reading position (oldest data lost)
write new data to buffer
update writing position
do wakeup on condition variable
unlock mutex

读者阅读:

lock mutex
loop:
    get current reading position in buffer
    compare to current writing position in buffer
    if there's new data, break loop
    wait (possibly with timeout) on condition variable
    goto loop:
copy data from buffer
update reading position
unlock mutex
process copied data

显然,在上面,writer 可能会短暂阻塞 mutex,但由于 reader 只会短暂持有 mutex(假设缓冲区中的数据相当短),这可能不是问题。

关于理解上述代码的重要细节:条件变量和互斥锁作为一对工作。等待条件变量将解锁互斥锁,一旦唤醒,只有在它可以重新锁定互斥锁后才会继续。所以在作者解锁互斥锁之前,阅读器实际上不会继续。

当条件变量等待返回时再次检查缓冲区位置很重要,不要盲目相信唤醒是由写入器完成的,它只是向它添加了更多数据。

【讨论】:

  • 非常感谢您的解释。我对 C 真的很陌生(昨天第一次使用 pthread),所以我需要几天的时间才能安全地实现它。我再次搜索了几个小时(除了我的真正工作),但没有找到完整的代码。也许有人已经有了完整的代码?再次感谢。
  • 也许可以通过互斥锁和触发器使用和扩展这个:github.com/dhess/c-ringbuf ?
  • @MartinM。我认为我的伪代码可以直接转换为 C,如果您要使用 pthreads,那将是学习它的好方法。我会看看我以后有没有时间写一些C(没有承诺),我写伪代码已经有一段时间了,写正确的伪代码要容易得多;)
  • 谢谢 :) 这对我来说绝对是一个好的开始。我找到了一段代码,它可能可以满足我的需要(使用 sem_wait 而不是 wait_event),如果它在最后有效,我也会提供反馈。也许我们可以在最后为其他人发布一个好的工作解决方案。
  • 对于信号量,相关问题:stackoverflow.com/questions/70773/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-27
  • 2011-03-03
  • 2022-08-20
  • 2015-10-16
  • 2020-05-15
相关资源
最近更新 更多