Skip to main content
 首页 » 编程设计

python 通过管道进行进程通信 : Race condition

2025年05月04日15zhujiabin

所以我有两个Python3.2进程需要相互通信。大多数需要交流的信息都是标准词典。命名管道似乎是可行的方法,所以我制作了一个可以在两个进程中实例化的管道类。这个类实现了一个非常基本的协议(protocol)来获取信息。

我的问题是它有时有效,有时无效。除了代码失败的地方外,此行为似乎没有任何模式。

以下是 Pipe 类中重要的部分。如果你想要更多代码,请大声喊:

class Pipe: 
    """ 
    there are a bunch of constants set up here. I dont think it would be useful to include them. Just think like this: Pipe.WHATEVER = 'WHATEVER' 
    """ 
    def __init__(self,sPath): 
        """ 
        create the fifo. if it already exists just associate with it 
        """ 
        self.sPath = sPath 
        if not os.path.exists(sPath): 
            os.mkfifo(sPath) 
        self.iFH = os.open(sPath,os.O_RDWR | os.O_NONBLOCK) 
        self.iFHBlocking = os.open(sPath,os.O_RDWR) 
 
    def write(self,dMessage): 
        """ 
        write the dict to the fifo 
        if dMessage is not a dictionary then there will be an exception here.  There never is  
        """ 
        self.writeln(Pipe.MESSAGE_START) 
        for k in dMessage: 
            self.writeln(Pipe.KEY) 
            self.writeln(k) 
            self.writeln(Pipe.VALUE) 
            self.writeln(dMessage[k]) 
        self.writeln(Pipe.MESSAGE_END) 
 
    def writeln(self,s): 
        os.write(self.iFH,bytes('{0} : {1}\n'.format(Pipe.LINE_START,len(s)+1),'utf-8')) 
        os.write(self.iFH,bytes('{0}\n'.format(s), 'utf-8')) 
        os.write(self.iFH,bytes(Pipe.LINE_END+'\n','utf-8')) 
 
    def readln(self): 
        """ 
        look for LINE_START, get line size 
        read until LINE_END 
        clean up 
        return string 
        """ 
        iLineStartBaseLength = len(self.LINE_START)+3  #'{0} : ' 
        try: 
            s = os.read(self.iFH,iLineStartBaseLength).decode('utf-8') 
        except: 
            return Pipe.READLINE_FAIL 
 
        if Pipe.LINE_START in s: 
            #get the length of the line 
            sLineLen = '' 
            while True: 
                try: 
                    sCurrent = os.read(self.iFH,1).decode('utf-8') 
                except: 
                    return Pipe.READLINE_FAIL 
                if sCurrent == '\n': 
                    break 
                sLineLen += sCurrent 
            try: 
                iLineLen = int(sLineLen.strip(string.punctuation+string.whitespace)) 
            except: 
                raise Exception('Not a valid line length: "{0}"'.format(sLineLen)) 
            #read the line 
            sLine = os.read(self.iFHBlocking,iLineLen).decode('utf-8') 
 
            #read the line terminator 
            sTerm = os.read(self.iFH,len(Pipe.LINE_END+'\n')).decode('utf-8') 
            if sTerm == Pipe.LINE_END+'\n': 
                return sLine 
            return Pipe.READLINE_FAIL 
 
        else: 
            return Pipe.READLINE_FAIL 
 
    def read(self): 
        """ 
        read from the fifo, make a dict 
        """ 
        dRet        = {} 
        sKey        = '' 
        sValue      = '' 
        sCurrent    = None 
 
        def value_flush(): 
            nonlocal dRet, sKey, sValue, sCurrent 
            if sKey: 
                dRet[sKey.strip()] = sValue.strip() 
            sKey = '' 
            sValue = '' 
            sCurrent = '' 
 
        if self.message_start(): 
            while True: 
                sLine = self.readln() 
                if Pipe.MESSAGE_END in sLine: 
                    value_flush() 
                    return dRet 
                elif Pipe.KEY in sLine: 
                    value_flush() 
                    sCurrent = Pipe.KEY 
                elif Pipe.VALUE in sLine: 
                    sCurrent = Pipe.VALUE 
                else: 
                    if sCurrent == Pipe.VALUE: 
                        sValue += sLine 
                    elif sCurrent == Pipe.KEY: 
                        sKey += sLine 
        else: 
            return Pipe.NO_MESSAGE 

它有时会在这里失败(在 readln 中):

        try: 
            iLineLen = int(sLineLen.strip(string.punctuation+string.whitespace)) 
        except: 
            raise Exception('Not a valid line length: "{0}"'.format(sLineLen)) 

它不会在其他任何地方失败。

一个示例错误是:

Not a valid line length: "KE 17" 

它断断续续的事实告诉我这是由于某种竞争条件造成的,我只是在努力弄清楚它可能是什么。有什么想法吗?

EDIT 添加了关于调用进程的内容

Pipe 的使用方法是在 processA 和 ProcessB 中通过调用具有相同路径的构造函数来实例化它。然后进程 A 将间歇性地写入管道,进程 B 将尝试从中读取。在任何时候我都不会尝试让这件事成为双向的。

这是对这种情况的更冗长的解释。我一直试图让问题简短,但我认为是时候放弃这个问题了。 Anyhoo,我有一个守护进程和一个金字塔进程需要很好地发挥作用。有两个 Pipe 实例在使用中:一个只有 Pyramid 写入,一个只有守护进程写入。 Pyramid 写的东西真的很短,我在这个管道上没有遇到任何错误。守护进程写的东西要长得多,这是让我悲伤的管道。两个管道的实现方式相同。两个进程都只将字典写入各自的 Pipes(如果不是这种情况,那么 Pipe.write 中就会出现异常)。

基本算法是:Pyramid 产生守护进程,守护进程加载疯狂的对象层次结构和大量的 ram 消耗。 Pyramid 将 POST 请求发送到守护进程,然后守护进程进行一大堆计算并将数据发送到 Pyramid,以便呈现一个人性化的页面。然后,人类可以通过填写 HTML 表单等方式响应层次结构中的内容,从而导致金字塔向守护进程发送另一个字典,然后守护进程发回字典响应。

因此:只有一个管道出现问题,问题管道的流量比另一个管道多得多,并且可以保证只向其中一个管道写入字典

编辑作为对问题和评论的回应

在你告诉我尝试之前...except 继续阅读。 完全引发异常的事实困扰着我。 iLineLengh = int(stuff) 在我看来应该始终传递一个看起来像整数的字符串。这只是大部分时间,而不是全部。因此,如果您想要评论它可能不是整数,请不要评论。

套用我的问题:发现竞争条件,你将成为我的英雄。

编辑一个小例子:

process_1.py:

oP = Pipe(some_path) 
while 1: 
    oP.write({'a':'foo','b':'bar','c':'erm...','d':'plop!','e':'etc'}) 

process_2.py:

oP = Pipe(same_path_as_before) 
while 1: 
    print(oP.read()) 

请您参考如下方法:

在尝试了代码之后,我怀疑问题出在您读取文件的方式上。

具体来说,像这样的行:

os.read(self.iFH, iLineStartBaseLength) 

该调用不一定返回 iLineStartBaseLength 字节 - 它可能会消耗 "LI" ,然后返回 READLINE_FAIL 并重试。在第二次尝试时,它将获取该行的剩余部分,并以某种方式最终将非数字字符串提供给 int() 调用

不可预测性可能来自于刷新 fifo 的方式 - 如果在写入完整行时恰好刷新,则一切正常。如果它在该行写到一半时刷新,那就奇怪了。

至少在我最终得到的脚本的破解版本中,process_2.py 中的 oP.read() 调用经常得到一个不同的指令发送的那个(KEY 可能会渗入之前的 VALUE 和其他奇怪的地方)。

我可能是错的,因为我必须进行大量更改才能使代码在 OS X 上运行,并且在进行试验时会做进一步的更改。 My modified code here

不确定如何修复它,但是..使用 json 模块或类似模块,可以大大简化协议(protocol)/解析 - 换行符分隔的 JSON 数据更容易解析:

import os 
import time 
import json 
import errno 
 
 
def retry_write(*args, **kwargs): 
    """Like os.write, but retries until EAGAIN stops appearing 
    """ 
 
    while True: 
        try: 
            return os.write(*args, **kwargs) 
        except OSError as e: 
            if e.errno == errno.EAGAIN: 
                time.sleep(0.5) 
            else: 
                raise 
 
 
class Pipe(object): 
    """FIFO based IPC based on newline-separated JSON 
    """ 
 
    ENCODING = 'utf-8' 
 
    def __init__(self,sPath): 
        self.sPath = sPath 
        if not os.path.exists(sPath): 
            os.mkfifo(sPath) 
 
        self.fd = os.open(sPath,os.O_RDWR | os.O_NONBLOCK) 
        self.file_blocking = open(sPath, "r", encoding=self.ENCODING) 
 
    def write(self, dmsg): 
        serialised = json.dumps(dmsg) + "\n" 
        dat = bytes(serialised.encode(self.ENCODING)) 
 
        # This blocks until data can be read by other process. 
        # Can just use os.write and ignore EAGAIN if you want 
        # to drop the data 
        retry_write(self.fd, dat) 
 
    def read(self): 
        serialised = self.file_blocking.readline() 
        return json.loads(serialised)