通过管道的python进程通信:竞争条件
所以我有两个Python3.2进程需要相互通信。大多数需要交流的信息都是标准词典。命名管道似乎是一种方法,所以我创建了一个管道类,可以在两个进程中实例化。这个类实现了一个非常基本的获取信息的协议 我的问题是,有时有效,有时无效。除了代码失败的地方之外,这种行为似乎没有任何模式 下面是管道类的一些重要部分。如果需要更多代码,请呼喊:通过管道的python进程通信:竞争条件,python,named-pipes,race-condition,python-3.2,Python,Named Pipes,Race Condition,Python 3.2,所以我有两个Python3.2进程需要相互通信。大多数需要交流的信息都是标准词典。命名管道似乎是一种方法,所以我创建了一个管道类,可以在两个进程中实例化。这个类实现了一个非常基本的获取信息的协议 我的问题是,有时有效,有时无效。除了代码失败的地方之外,这种行为似乎没有任何模式 下面是管道类的一些重要部分。如果需要更多代码,请呼喊: class Pipe: """ there are a bunch of constants set up here. I dont think it
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中):
它不会在其他任何地方失败
一个示例错误是:
Not a valid line length: "KE 17"
事实上,它是间歇性的,这对我来说是由于某种比赛条件造成的,我只是在努力弄清楚它可能是什么。有什么想法吗
编辑添加了有关调用进程的内容
管道的使用方式是:通过调用具有相同路径的构造函数,在processA和ProcessB中实例化管道。然后,进程A将间歇地向管道写入数据,而进程B将尝试从中读取数据。在任何时候,我都不会试图让这个东西成为一个双向的
这里有一个更冗长的情况解释。我一直想把问题简短一点,但我想是时候放弃了。总之,我有一个守护进程和一个金字塔进程,需要好好玩。有两个正在使用的管道实例:一个只对金字塔进行写入,另一个只对守护进程进行写入。金字塔写的东西真的很短,我在这个管道上没有遇到任何错误。守护进程写的东西要长得多,这是给我带来悲伤的管道。这两个管道的实现方式相同。这两个进程只将字典写入各自的管道(如果不是这样,那么Pipe.write中将出现异常)
基本算法是:金字塔产生守护进程,守护进程加载疯狂的对象层次结构和巨大的内存消耗。Pyramid将POST请求发送给守护进程,守护进程随后执行一系列计算并将数据发送给Pyramid,以便呈现人性化的页面。然后,人类可以通过填充HTML表单等方式响应层次结构中的内容,从而导致pyramid向守护进程发送另一个字典,守护进程返回字典响应
所以:只有一个管道出现了问题,有问题的管道比另一个管道的流量大得多,这是一个保证,这两个管道都只编写字典
编辑作为对问题和评论的回应
在你告诉我去尝试之前…除了读下去的东西。
异常被提出的事实是困扰我的。在我看来,应该始终向它传递一个看起来像整数的字符串。大多数情况下都是这样,而不是全部。所以,如果你有强烈的欲望去评论它怎么可能不是一个整数,请不要
套用我的问题:发现种族状况,你将成为我的英雄
编辑一个小例子:
过程1.py:
oP = Pipe(some_path)
while 1:
oP.write({'a':'foo','b':'bar','c':'erm...','d':'plop!','e':'etc'})
过程2.py:
oP = Pipe(same_path_as_before)
while 1:
print(oP.read())
试着摆脱
Try:
,except:
块,看看实际抛出了什么异常
因此,将您的样本替换为:
iLineLen = int(sLineLen.strip(string.punctuation+string.whitespace))
我打赌它现在会抛出一个ValueError
,这是因为您试图将“KE 17”转换为int
你需要去除的不仅仅是
string.whitespace
和string.标点符号
,如果你想在玩完代码后将字符串转换为int
,我怀疑问题在于你如何读取文件
具体来说,像这样的行:
os.read(self.iFH, iLineStartBaseLength)
该调用不一定返回iListartBaseLength
字节-它可能会消耗“LI”
,然后返回READLINE\u FAIL
并重试。在第二次尝试中,它将获得行的剩余部分,并以某种方式将非数字字符串提供给int()
调用
不可预测性可能来自fifo的刷新方式——如果在写入完整行时恰好刷新,则一切正常。如果在写了一半的时候它会变红,那就是奇怪
至少在我最终使用的脚本的黑客版本中,oP.read()
调用进程2.py
通常会得到与发送的脚本不同的dict(其中键
可能会渗入先前的值
和其他奇怪之处)
我可能弄错了,因为为了让代码在OSX上运行,我不得不做了一系列的更改,并且在实验过程中做了进一步的更改
不知道该如何修复,但是。。使用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)
很明显,引发异常是因为
“KE 17”
无法转换为整数——你知道为什么有一个进程会写这个吗?@mgilson:gosh,真的吗?我对我的问题稍加修改,也许现在会更有意义。我很确定这是竞争条件的结果,因为它是间歇性的,并且总是在同一个位置。当它失败时,它会在您最初运行.read()
-调用过程时发生吗?或者它会在中途发生吗?此外,拥有完整版本的代码也很有用,发布的代码包含了大部分代码,但缺少管道。。。所有常量都是同名字符串。这只是一种防止愚蠢的打字错误的方法。。。我现在正在处理缩进。它并不总是发生在第一次跑步时,有时会,有时会发生在完全不同的跑步中。没有我能确定的模式
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)