0

在尝试实现Named pipe(例如,使用相同共享内存的两个独立的不相关进程)时,我一直在阅读我将使用pthread_atforkatexit.

我完全同意使用互斥体和信号量——使用它们我们可以决定何时process A读/写以及何时process B读/写。

但是出于什么原因我想为此使用pthread_atfork和线程?

编辑:

一个不使用信号量会付出高昂代价的例子:

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <assert.h>

 // Simple busy-wait loop to throw off our timing.
 void busywait(void)
 {
     clock_t t1 = times(NULL);
     while (times(NULL) - t1 < 2);
 }


 int main(int argc, char *argv[])
 {
     const char *message = "Hello World\n";
     int n = strlen(message) / 2;

     pid_t pid = fork();
     int i0 = (pid == 0) ? 0 : n;
     int i;

     for (i = 0; i < n; i++) {
         write(1, message + i0 + i, 1);
         busywait();
     }
 }
4

2 回答 2

1

没有人说过您必须使用线程来实现命名管道。但是您的库代码可以在线程化的项目中使用,因此您可以处理许多特殊情况。正如您可能知道的那样,SysV IPC 对象(如共享内存段)不会在其使用计数下降到时自动删除,0除非它们已被标记为销毁。这意味着如果程序使用您的代码创建管道然后由于任何原因崩溃,则管道实现中的 IPC 对象很可能会保留,污染 IPC 命名空间,并消耗宝贵的系统资源。

您提到的两个函数,pthread_atfork()用于atexit()注册在发生某些事情时执行的回调。atexit()注册要在进程以正常方式终止时执行的代码(例如,通过调用exit(3)或从返回main())。这使您可以捕获管道未明确关闭的情况并进行必要的清理。

除了在不关闭管道的情况下退出进程外,程序还可能会自行分叉。这也是您必须相应处理的特殊情况。pthread_atfork()应该注册三个回调,以便在进行分叉时在各个点调用。

您还应该处理某些操作系统信号,否则这些信号可能最终无法捕获,这可能会在执行适当的清理之前终止程序。

如您所见,编写库比编写程序要复杂得多。在编写程序时,您控制(几乎)所有用例。当您编写一个库时,它可能会用于许多不同的场景,您应该考虑所有这些场景并为所有这些场景做好准备。您应该考虑正确的用法和错误的用法。您应该考虑诸如清理之类的事情,以及如果您的库未正确使用,哪些系统资源可能会滞留。等等等等...

于 2012-07-19T10:56:34.980 回答
0

粗略地说,您的类似管道的结构应该位于共享内存中(例如,通过 获得shm_open)并包含以下成员:

  • 一个互斥锁控制对结构的所有访问。
  • 一个用于向等待对等方发出信号的条件变量。
  • 一些固定大小的缓冲区。
  • 当前读写位置的缓冲区的两个偏移量。

读取函数基本上应该是获取互斥体,然后检查是否有数据可供读取,如果没有,则等待条件变量并循环重新检查数据。找到数据后,如果读取可以在缓冲区中容纳更多数据以唤醒可能等待的写入器,则需要向条件变量发出信号。将读取的数据复制到调用者提供的缓冲区中,然后解锁互斥锁。

写函数基本上应该是获取互斥体,然后检查缓冲区中是否还有空间可以写。如果不是,它应该等待条件变量并循环重新检查可写入的空间。一旦找到空间,它应该将数据复制到缓冲区中,向条件变量发出信号以唤醒任何可能正在等待数据的读取器,并解锁互斥锁。

互斥体和条件变量都需要使用进程共享属性创建,您将使用“管道”在进程之间进行通信(不仅仅是同一进程中的线程)。您可能还需要考虑是否要支持多个读取器/写入器以及条件变量是否需要单信号或广播信号语义。有很多方法可以优化行为,但上面的大纲应该给你一个大致的起点。

于 2012-07-18T13:42:48.790 回答