为啥这里举例说明在 LINUX 中使用命名管道 -FIFO 的程序会遇到竞争条件?
Posted
技术标签:
【中文标题】为啥这里举例说明在 LINUX 中使用命名管道 -FIFO 的程序会遇到竞争条件?【英文标题】:Why the program here exemplifying the usage of named pipe -FIFO in LINUX suffers from race condition?为什么这里举例说明在 LINUX 中使用命名管道 -FIFO 的程序会遇到竞争条件? 【发布时间】:2014-02-01 13:07:39 【问题描述】:我有两个程序 - 一个程序写入管道,另一个程序从管道读取。但是,当我运行它们时,会丢失消息。
writer 程序如下 -
执行时的写入过程——
$ ./pipe.out
Write to Buffer = 0
执行时的读取过程-
$ ./pipe_reader.out
Pipe already exists
Read from Buffer
0
Read from Buffer
7
Read from Buffer
7
Read from Buffer
您是否看到这里的消息丢失了?
这是程序,请解释这里的问题是什么?如果我不这样做,同样的工作 关闭文件描述符。事实上,继续打开它。我相信这会导致其他一些问题。在这里,它可以工作,因为打开的 fd 只有 10。
程序 - 类
#include "iostream"
#include "unistd.h"
#include "stdio.h"
#include "fcntl.h"
#include "string.h"
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
class Named_Pipe
private:
int m_fp;
char m_name[256];
int m_rd;
int m_wr;
public:
void open_pipe(char *name);
void close_reader();
void close_writer();
void write_to_pipe(char *msg);
void read_from_pipe(char *msg);
Named_Pipe();
;
Named_Pipe::Named_Pipe()
/** no access to any groups and others **/
umask(077);
void Named_Pipe::open_pipe(char *name)
int rc;
struct stat pipe_exist;
strcpy(m_name,name);
if ( stat(name, &pipe_exist) == 0 )
cout<<"Pipe already exists"<<endl;
else
rc = mkfifo(name, S_IRWXU);
if( rc == -1)
cout<<strerror(rc);
void Named_Pipe::write_to_pipe(char *msg)
int rc = 0;
m_wr = open(m_name,O_WRONLY);
if( m_wr == -1)
cout<<"Error in opening the descriptor for writing"<<endl;
rc = write(m_wr,msg,256);
if( rc == -1)
cout<<"Error in writing the message"<<endl;
void Named_Pipe::read_from_pipe(char *msg)
int rc = 0;
m_rd = open(m_name,O_RDONLY);
if( m_rd == -1)
cout<<"Error in opening the descriptor for reading"<<endl;
rc = read(m_rd,msg,256);
if( rc == -1)
cout<<"Error in reading the message"<<endl;
void Named_Pipe:: close_reader()
close(m_rd);
void Named_Pipe:: close_writer()
close(m_wr);
现在,作者管道流程逻辑 -
管道.cpp
int main(int argc, char *argv[])
Named_Pipe write_process;
char buffer[256];
int i = 0;
write_process.open_pipe("FIRST_FIFO_PROG");
for( i= 0; i<10; i++)
strcpy(buffer,"MY FIRST MSG ON PIPES");
cout<<"Write to Buffer = "<< i<< endl;
sprintf(buffer,"%d",i);
write_process.write_to_pipe(buffer);
write_process.close_writer();
return 0;
现在,这里是读取器管道进程。
int main(int argc, char *argv[])
Named_Pipe read_process;
char buffer[256];
int i = 0;
read_process.open_pipe("FIRST_FIFO_PROG");
for( i= 0; i<10; i++)
cout<<"Read from Buffer"<<endl;
read_process.read_from_pipe(buffer);
cout<<buffer<<endl;
read_process.close_reader();
return 0;
【问题讨论】:
我不认为这是因为竞争条件。请向我们展示实现资源锁定的代码 这是我的全部代码 方法在哪里? 【参考方案1】:您不断地在读写端打开和关闭 FIFO。您只需打开一次,写入(和读取)您的消息,然后关闭 FIFO。
您所看到的与其说是比赛条件,不如说是您自己制造的计时问题。在相应的打开调用成功之前,一个 FIFO 需要一个读取器和一个写入器,并且一个 FIFO 可以有多个读取器和写入器。
我看到的是以下变化:
-
读写器打开
作家写道
作者关闭
步骤 2 和 3 循环 x 次 之前...
读者阅读一条消息
阅读器关闭,其余消息丢失。
你还写了一个固定的 256 字节,你可能想写 strlen
字节。并且一如既往地循环读写。
【讨论】:
我的理解是数据是在管道文件中写入的。现在我可以打开该文件来读取或写入消息。该消息仅在阅读后才被删除。为什么关闭会导致问题? 那可能是你误解的症结所在。当 FIFO 的所有描述符都关闭时,数据将被丢弃。 是的,这是我的误会。【参考方案2】:请不要接受这个答案——接受Duck的answer。
分析
您的类成员函数操作与其名称不匹配。
构造函数只设置umask()
,不做任何其他事情。它甚至不会将文件描述符设置为已知状态。
如果没有该名称的文件,open_pipe()
函数会创建 FIFO。
write_to_pipe()
函数在每次被调用时都会打开文件并写入管道而不关闭它。
read_from_pipe()
函数在每次被调用时都会打开文件并从管道中读取而不关闭它。
close_reader()
和 close_writer()
函数关闭文件描述符,无论是否执行了相应的读取或写入。
没有析构函数。
从不使用类的m_fp
成员变量。
尽管有这么多问题,但代码表面上应该可以工作——除了当所有文件描述符都关闭时,FIFO 会丢弃任何写入的数据。 您从commentDuck 获得此关键信息。
请注意,如果您的代码在调用 close 之前进行了多次读取或写入,那么您将严重泄漏资源。
这是一个重写——处理一些设计问题。
管道.h
#ifndef PIPE_H_INCLUDED
#define PIPE_H_INCLUDED
class Named_Pipe
private:
char m_name[256];
int m_rd;
int m_wr;
bool m_mk; // FIFO created?
void mkpipe(char const *name);
public:
void open_reader(const char *name);
void open_writer(const char *name);
void close_reader();
void close_writer();
void write_to_pipe(const char *msg);
int read_from_pipe(char *msg, int maxlen);
Named_Pipe();
~Named_Pipe();
;
#endif // PIPE_H_INCLUDED
管道.cpp
#include "pipe.h"
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <fcntl.h>
#include <iostream>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;
Named_Pipe::Named_Pipe() : m_rd(-1), m_wr(-1), m_mk(false)
m_name[0] = '\0';
umask(077);
Named_Pipe::~Named_Pipe()
close_reader();
close_writer();
unlink(m_name);
void Named_Pipe::mkpipe(char const *name)
struct stat pipe_exist;
strcpy(m_name, name);
if (stat(name, &pipe_exist) != 0)
if (mkfifo(name, S_IRWXU) != 0)
cerr << strerror(errno);
exit(1);
m_mk = true;
void Named_Pipe::open_reader(char const *name)
if (!m_mk)
mkpipe(name);
m_rd = open(m_name, O_RDONLY);
if (m_rd == -1)
cerr << "Error in opening " << name << " for reading" << endl;
exit(1);
void Named_Pipe::open_writer(char const *name)
if (!m_mk)
mkpipe(name);
m_wr = open(m_name, O_WRONLY);
if (m_wr == -1)
cerr << "Error in opening FIFO " << name << " for writing" << endl;
exit(1);
void Named_Pipe::write_to_pipe(char const *msg)
if (m_wr == -1)
cerr << "Writing to unopened FIFO\n";
exit(1);
int len = strlen(msg) + 1;
if (write(m_wr, msg, len) != len)
cerr << "Error in writing the message" << endl;
exit(1);
int Named_Pipe::read_from_pipe(char *msg, int msglen)
if (m_rd == -1)
cerr << "Reading from unopened FIFO\n";
exit(1);
int rc = read(m_rd, msg, msglen - 1);
if (rc == -1)
cerr << "Error in reading the message" << endl;
else if (rc == 0)
cerr << "EOF on pipe" << endl;
else if (msg[rc-1] != '\0')
msg[rc] = '\0';
cerr << "Read " << rc << " bytes from FIFO\n";
return rc;
void Named_Pipe::close_reader()
if (m_rd != -1)
close(m_rd);
m_rd = -1;
void Named_Pipe::close_writer()
if (m_wr != -1)
close(m_wr);
m_wr = -1;
管道阅读器.cpp
#include "pipe.h"
#include <iostream>
#include <cstring>
using namespace std;
int main()
Named_Pipe read_process;
char buffer[256];
int i = 0;
read_process.open_reader("FIRST_FIFO_PROG");
for (i = 0; i < 10; i++)
int nbytes = read_process.read_from_pipe(buffer, sizeof(buffer));
const char *data = buffer;
int counter = 0;
while (nbytes > 0)
int len = strlen(data);
cout << "Reader" << counter << ": [" << data << "]" << endl;
nbytes -= len + 1;
data += len + 1;
read_process.close_reader();
cout << "Reader complete\n";
return 0;
pipe-writer.cpp
#include "pipe.h"
#include <iostream>
#include <cstring>
using namespace std;
int main()
Named_Pipe write_process;
char buffer[256];
write_process.open_writer("FIRST_FIFO_PROG");
for (int i = 0; i < 10; i++)
sprintf(buffer, "Message on FIFO %d", i);
cout << "Write to Buffer = [" << buffer << "]" << endl;
write_process.write_to_pipe(buffer);
write_process.close_writer();
cout << "Writer complete\n";
return 0;
示例输出
示例 1:
$ pipe-writer & sleep 1 ; pipe-reader
[1] 9576
Write to Buffer = [Message on FIFO 0]
Write to Buffer = [Message on FIFO 1]
Write to Buffer = [Message on FIFO 2]
Write to Buffer = [Message on FIFO 3]
Write to Buffer = [Message on FIFO 4]
Write to Buffer = [Message on FIFO 5]
Write to Buffer = [Message on FIFO 6]
Write to Buffer = [Message on FIFO 7]
Write to Buffer = [Message on FIFO 8]
Write to Buffer = [Message on FIFO 9]
Writer complete
Read 180 bytes from FIFO
Reader0: [Message on FIFO 0]
Reader1: [Message on FIFO 1]
Reader2: [Message on FIFO 2]
Reader3: [Message on FIFO 3]
Reader4: [Message on FIFO 4]
Reader5: [Message on FIFO 5]
Reader6: [Message on FIFO 6]
Reader7: [Message on FIFO 7]
Reader8: [Message on FIFO 8]
Reader9: [Message on FIFO 9]
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
Reader complete
[1]+ Done pipe-writer
$
示例 2:
$ pipe-writer & pipe-reader
[1] 9579
Write to Buffer = [Message on FIFO 0]
Write to Buffer = [Message on FIFO 1]
Write to Buffer = [Message on FIFO 2]
Write to Buffer = [Message on FIFO 3]
Write to Buffer = [Message on FIFO 4]
Write to Buffer = [Message on FIFO 5]
Read Write to Buffer = [Message on FIFO 6]
Write to Buffer = [Message on FIFO 7]
Write to Buffer = [Message on FIFO 8]
Write to Buffer = [Message on FIFO 9]
36Writer complete
bytes from FIFO
Reader0: [Message on FIFO 0]
Reader1: [Message on FIFO 1]
Read 144 bytes from FIFO
Reader0: [Message on FIFO 2]
Reader1: [Message on FIFO 3]
Reader2: [Message on FIFO 4]
Reader3: [Message on FIFO 5]
Reader4: [Message on FIFO 6]
Reader5: [Message on FIFO 7]
Reader6: [Message on FIFO 8]
Reader7: [Message on FIFO 9]
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
EOF on pipe
Read 0 bytes from FIFO
Reader complete
[1]+ Done pipe-writer
$
现在你可以看到为什么阅读器主程序中有额外的循环了。
我仍然不认为这是一个好的设计。类中应该只有一个文件描述符。 FIFO 的名称应传递到构造函数中,并指示这是用于读取还是写入。两个程序中复制了 FIFO 的名称;应使用通用名称。我保留了close_xxxxer()
函数,但最好让析构函数完成这项工作。您可以将函数名称简化为read()
和write()
。代码错误检查但写入cerr
(不是cout
)并在错误时退出。注意-1的返回码和errno
得到的错误号是不一样的。
【讨论】:
【参考方案3】:#include "iostream"
#include "unistd.h"
#include "stdio.h"
#include "fcntl.h"
#include "string.h"
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
class Named_Pipe
private:
int m_fp;
char m_name[256];
int m_rd;
int m_wr;
public:
void open_pipe(char *name);
void close_reader();
void close_writer();
void write_to_pipe(char *msg);
void read_from_pipe(char *msg);
Named_Pipe(int read_desc, int write_desc);
~Named_Pipe();
;
Named_Pipe::Named_Pipe(int read_desc, int write_desc):m_rd(read_desc), m_wr(write_desc)
/** no access to any groups and others **/
umask(077);
Named_Pipe::~Named_Pipe()
/** This is to remove the pipe create **/
unlink(m_name);
void Named_Pipe::open_pipe(char *name)
int rc;
struct stat pipe_exist;
strcpy(m_name,name);
if ( stat(name, &pipe_exist) == 0 )
cout<<"Pipe already exists"<<endl;
else
rc = mkfifo(name, S_IRWXU);
if( rc == -1)
cout<<strerror(rc);
void Named_Pipe::write_to_pipe(char *msg)
int rc = 0;
if ( m_wr == -1)
m_wr = open(m_name,O_WRONLY);
if( m_wr == -1)
cout<<"Error in opening the descriptor for writing"<<endl;
rc = write(m_wr,msg,256);
if( rc == -1)
cout<<"Error in writing the message"<<endl;
void Named_Pipe::read_from_pipe(char *msg)
int rc = 0;
if( m_rd != 0)
m_rd = open(m_name,O_RDONLY);
if( m_rd == -1)
cout<<"Error in opening the descriptor for reading"<<endl;
rc = read(m_rd,msg,256);
if( rc == -1)
cout<<"Error in reading the message"<<endl;
void Named_Pipe:: close_reader()
close(m_rd);
void Named_Pipe:: close_writer()
close(m_wr);
现在,分别是写入者和读取者进程。
int main(int argc, char *argv[])
/* reader and writer , -1 un-initialized and 0 means intialized **/
Named_Pipe write_process(0,-1);
char buffer[256];
int i = 0;
write_process.open_pipe("FIRST_FIFO_PROG");
for( i= 0; i<10; i++)
sprintf(buffer,"%s %d","MY FIRST MSG ON PIPES",i);
cout<<"Write Buffer = "<< buffer<< endl;
write_process.write_to_pipe(buffer);
write_process.close_writer();
return 0;
阅读器进程-
int main(int argc, char *argv[])
/* reader and writer , -1 un-initialized and 0 means intialized **/
Named_Pipe read_process(-1,0);
char buffer[256];
int i = 0;
read_process.open_pipe("FIRST_FIFO_PROG");
for( i= 0; i<10; i++)
read_process.read_from_pipe(buffer);
cout<<"Read Buffer = "<< buffer<< endl;
read_process.close_reader();
return 0;
【讨论】:
以上是关于为啥这里举例说明在 LINUX 中使用命名管道 -FIFO 的程序会遇到竞争条件?的主要内容,如果未能解决你的问题,请参考以下文章