为啥这里举例说明在 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 的程序会遇到竞争条件?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个命名管道不打印发送的行?

为啥只读打开命名管道块?

实验八进程间通信

Linux进程间通信 -- 使用命名管道

为啥命名管道是本地的?

命名管道