多线程的 Fifo 计时。一个线程对 fifo 的两次写入仅适用于 sleep(1)

Fifo timing with multiple threads. Two writes to fifo from one thread only work with sleep(1)

我正在制作一个应用程序,它在开始时分成两个进程。简单来说,其中一个进程不断地从一个 fifo 文件中读取,另一个进程偶尔将通知写入 fifo 文件。现在,一切正常,除非写入过程 writes 连续快速调用它的写入方法。然后只有第一个通知从 fifo 文件中写入(或读取?)。

这是fifo读取过程的代码:

void logging(){
    int fd1;

    char * myfifo = "/home/jens/Desktop/CLion_projects/Labo9/logfifo";
    mkfifo(myfifo, 0666);
    char str1[20];
    while(1) {
        pthread_mutex_lock(&lock_fifo);
        fd1 = open(myfifo,O_RDONLY);                            
        read(fd1, str1, 1000);

        printf("%s\n", str1);
        close(fd1);
        pthread_mutex_unlock(&lock_fifo);
    }
}

这是写入fifo文件的方法(在其他进程中):

void write_to_fifo(char * string_to_write){
    int fd;
    char * myfifo = "/home/jens/Desktop/CLion_projects/Labo9/logfifo";
//    mkfifo(myfifo, 0666);                             (should this be on or not?)

    char * arr2 = string_to_write;
    fd = open(myfifo, O_WRONLY);
    write(fd, arr2, strlen(arr2)+1);
    close(fd);
}

这是两个使用 write_to_fifo(..) 方法的调用:

    write_to_fifo("Connection to SQL server established.\n");
//    sleep(1);
    write_to_fifo("New table "TO_STRING(TABLE_NAME)" created.\n");

第一个始终正确打印,第二个仅在取消注释 sleep(1) 时有效。因此,我猜这与时间有关。如果我不是同时 运行 多个线程,那么将 sleep(1) 留在那里不是问题。我想象 运行 多个线程使时间不可预测,并且您不能在不同线程的函数调用之间添加 sleep(1) 行。

  1. 为什么这个程序只有在引入延迟时才有效?
  2. 这是应该的样子吗?
  3. 如果没有,我该如何克服?
  1. Why does this program only work when the delay is introduced?

因为 FIFO 是 stream-oriented,而不是 message-oriented。假设一个 write() 调用写入的数据将被任何 read() 调用作为一个完整的单元读取是不安全的。在您的特定情况下,如果您的 write_to_fifo() 函数被快速连续调用多次,那么您可能会在读取之间发生两次或多次写入,在这种情况下,一次读取可能会从两次写入中获取所有数据,而不仅仅是从第一个。

此外,如果发生这种情况,那么它对您是隐藏的,因为您的写入(似乎是故意的)包含字符串终止符。无论 reader 读取多少,它的 printf() 将只输出到第一个终止符的数据。也就是说,我认为没有理由认为您正在丢失 fifo 中的消息;相反,我相信您会在 reader.

中失去它们
  1. Is this how it is supposed to be?

如上所述,你描述的行为对我来说似乎是一致的。

  1. If not, how do I overcome this?

即使行为在某种意义上是“应该如何”,但这并不意味着您无法获得更喜欢的行为。由于(几乎可以肯定)丢失消息的是 reader,您可以通过使 reader 更智能来解决此问题。即使不修改 writer 也应该能够做到这一点,但修改 writer 可能会更容易,而且还有其他原因可能导致您想要修改它。

首先,你必须始终考虑read()write()函数的return值,甚至比大多数职能。 return 值不仅会报告错误情况和(对于 read())end-of-file 情况,还会告诉您每次呼叫传输的字节数。这对双方来说都是重要的信息,因为任一函数传输的数字都可能小于请求的数字。一般来说,必须准备好在循环中调用 write()read() 以确保传输所有需要的字节。在您的特定情况下,您不一定需要读取一定数量的字节,但注意实际读取了多少字节可以让您识别何时通过相同的读取调用获得了多条消息的一部分。

其次,虽然 null-terminated 字符串是一个不错的 in-memory 表示,但它们并不是特别好的 on-the-wire 表示。由于您显然想打印每条消息后跟一个换行符,因此您可以考虑使用 newline-terminated 数据。在那种情况下,reader 甚至可能不需要担心消息边界——如果你想让它做的只是将消息转储到标准输出,那么它可能只是从 fifo 中读取并转储所有内容(使用字节数)到输出文件,包括数据中的换行符,而不用担心消息边界。

但是,如果您想以 per-message 为单位处理 variable-length 消息,那么更好的协议会对您有所帮助。例如,以 fixed-length 消息长度后跟该消息字节数的形式发送消息。这样,reader 总是知道要读取多少字节(即使它必须使用多个 read() 调用来获取它们)。

第三,reader写手不要一直打开和关闭fifo。每个进程都应该打开它一次,并在需要时保持打开状态。一个或两个都可以创建 fifo,一次,但如果你让他们都这样做,那么你需要准备至少一个不能这样做(因为另一个先做了)。编写器的多个线程可以共享同一个文件描述符,事实上,这通常比多个线程都单独打开同一个文件更可取。您可以考虑让编写器线程在每条消息后调用 fsync() 而不是关闭文件,但如果它们都使用相同的 FD,则可能没有必要这样做。

在多线程的情况下,对所有线程使用相同的 FD,您无需担心来自一个线程的写入调用的数据与来自不同线程的写入调用的数据交错。但是,您 do 需要注意,如果出站消息最终被拆分为多个 write 调用,那么您有时可能会在其中插入另一个线程的写入。您可以使用互斥锁来确保消息在分散到多个写入时不会以这种方式拆分。

但是,如果只有一个 reader,那么我不清楚在该侧使用互斥体会获得什么。