所以我有两个需要相互通信的Python3.2进程。需要传达的大多数信息都是标准词典。命名管道似乎是要走的路,所以我创建了一个可以在两个进程中实例化的管道类。这个类实现了一个非常基本的协议来获取信息。
我的问题是,有时候它会起作用,有时则不然。除了代码失败的地方外,似乎没有这种行为的模式。
以下是重要的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"
这是间歇性的事实告诉我,这是由于某种竞争条件,我只是在努力弄清楚它可能是什么。有什么想法吗?
编辑添加了有关调用流程的内容
如何使用Pipe是通过调用具有相同路径的构造函数在processA和ProcessB中实例化的。然后,进程A将间歇性地写入管道,进程B将尝试从中读取。在任何时候我都不会尝试让事情像双向一样。
这是对情况的更长期的解释。我一直在努力保持这个问题的简短,但我认为这是我放弃这个问题的时候了。 Anyhoo,我有一个守护进程和一个金字塔进程,需要玩得很好。正在使用两个Pipe实例:一个只有Pyramid写入,另一个只有守护进程写入。 Pyramid写的东西真的很短,我在这个管道上没有遇到任何错误。守护进程写的东西要长得多,这就是给我带来悲伤的管道。两个管道都以相同的方式实现。这两个进程只将字典写入各自的Pipes(如果不是这种情况,那么Pipe.write中会出现异常)。
基本算法是:金字塔产生守护进程,守护进程加载死亡的狂热对象层次结构和巨大的ram消耗。 Pyramid向守护进程发送POST请求,后者执行一大堆计算并将数据发送到Pyramid,以便可以呈现人性化页面。然后,人类可以通过填写HTML表单等来响应层次结构中的内容,从而使金字塔向守护进程发送另一个字典,守护进程发回字典响应。
所以:只有一个管道出现了任何问题,问题管道的流量比另一个管道多得多,并且只保留字典写入
的保证。编辑作为对问题和评论的回复
在你告诉我尝试之前......除了阅读的内容。 异常被提出的事实是困扰我的事情。 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())
答案 0 :(得分:1)
在玩完代码之后,我怀疑问题来自你如何阅读文件。
具体来说,这样的行:
os.read(self.iFH, iLineStartBaseLength)
该调用不一定返回iLineStartBaseLength
个字节 - 它可能会消耗"LI"
,然后返回READLINE_FAIL
并重试。在第二次尝试时,它将获得该行的其余部分,并以某种方式最终将非数字字符串提供给int()
调用
不可预测性可能来自于fifo如何被刷新 - 如果在写完整行时碰巧刷新,一切都很好。如果它在行写半写时会刷新,那就很奇怪。
至少在我最终使用的黑客版本的脚本中,oP.read()
中的process_2.py
电话通常与发送的电话有不同的字典(KEY
可能会流入前一个VALUE
和其他陌生感。)
我可能会弄错,因为我必须进行一系列更改才能在OS X上运行代码,并进一步进行实验。 My modified code here
不确定如何修复它,但是..使用json
模块或类似物,协议/解析可以大大简化 - 换行分离的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)
答案 1 :(得分:-1)
尝试删除try:
,except:
块并查看实际抛出的异常。
所以只需用以下代码替换样品:
iLineLen = int(sLineLen.strip(string.punctuation+string.whitespace))
我打赌它现在会抛出ValueError
,这是因为你试图将“KE 17”投射到int
。
如果您要将字符串转换为string.whitespace
,则需要删除超过string.punctuation
和int
的内容。