我有一个大的XML数据文件(> 160M)要处理,看起来像SAX / expat / pulldom解析是要走的路。我希望有一个线程可以筛选节点并将节点推送到队列中,然后其他工作线程将下一个可用节点拉出队列并进行处理。
我有以下(它应该有锁,我知道 - 它会,稍后)
import sys, time
import xml.parsers.expat
import threading
q = []
def start_handler(name, attrs):
q.append(name)
def do_expat():
p = xml.parsers.expat.ParserCreate()
p.StartElementHandler = start_handler
p.buffer_text = True
print("opening {0}".format(sys.argv[1]))
with open(sys.argv[1]) as f:
print("file is open")
p.ParseFile(f)
print("parsing complete")
t = threading.Thread(group=None, target=do_expat)
t.start()
while True:
print(q)
time.sleep(1)
问题是while
块的主体只被调用一次,然后我甚至无法ctrl-C中断它。在较小的文件上,输出是预期的,但这似乎表明只有在完全解析文档时才调用处理程序,这似乎违背了SAX解析器的目的。
我确定这是我自己的无知,但我不知道我在哪里弄错了。
PS:我也尝试过改变start_handler
:
def start_handler(name, attrs):
def app():
q.append(name)
u = threading.Thread(group=None, target=app)
u.start()
但是,没有爱。
答案 0 :(得分:7)
我对这个问题不太确定。我猜测对ParseFile的调用是阻塞的,因为GIL只运行解析线程。解决这个问题的方法是使用multiprocessing
代替。无论如何,它的目的是与队列一起工作。
import sys, time
import xml.parsers.expat
import multiprocessing
import Queue
def do_expat(q):
p = xml.parsers.expat.ParserCreate()
def start_handler(name, attrs):
q.put(name)
p.StartElementHandler = start_handler
p.buffer_text = True
print("opening {0}".format(sys.argv[1]))
with open(sys.argv[1]) as f:
print("file is open")
p.ParseFile(f)
print("parsing complete")
if __name__ == '__main__':
q = multiprocessing.Queue()
process = multiprocessing.Process(target=do_expat, args=(q,))
process.start()
elements = []
while True:
while True:
try:
elements.append(q.get_nowait())
except Queue.Empty:
break
print elements
time.sleep(1)
我已经包含了一个元素列表,只是为了复制原始脚本。您的最终解决方案可能会使用get_nowait
和Pool
或类似的东西。
答案 1 :(得分:7)
ParseFile
,正如您已经注意到的那样,只需“吞下”所有内容 - 对于您想要执行的 incremental 解析没有任何好处!因此,只需一次将文件提供给解析器,确保有条件地控制其他线程 - 例如:
while True:
data = f.read(BUFSIZE)
if not data:
p.Parse('', True)
break
p.Parse(data, False)
time.sleep(0.0)
time.sleep(0.0)
调用是Python的说法“如果有任何准备就绪并等待其他线程”; Parse
方法已记录在案here。
第二点是,忘记这个用法的锁! - 使用Queue.Queue代替,它本质上是线程安全的,几乎总是在Python中协调多个线程的最佳和最简单的方法。只需在其上创建一个Queue
实例q
,q.put(name)
,并在q.get()
上进行线程阻塞等待进行更多工作 - 这很简单!
(当没有更多的工作要做时,你可以使用几种辅助策略来协调工作线程的终止,但是最简单的,没有特殊要求的是,只要让它们成为守护进程线程,所以它们都将终止主线程的时候 - 见the docs)。
答案 2 :(得分:1)
我唯一看到的错误是你从不同的线程同时访问q
- 你写的确没有锁定。这就是在寻找麻烦 - 而且你可能会以Python解释器锁定你的方式遇到麻烦。 :)
尝试锁定,这真的不是很难:
import sys, time
import xml.parsers.expat
import threading
q = []
q_lock = threading.Lock() <---
def start_handler(name, attrs):
q_lock.acquire() <---
q.append(name)
q_lock.release() <---
def do_expat():
p = xml.parsers.expat.ParserCreate()
p.StartElementHandler = start_handler
p.buffer_text = True
print("opening {0}".format(sys.argv[1]))
with open(sys.argv[1]) as f:
print("file is open")
p.ParseFile(f)
print("parsing complete")
t = threading.Thread(group=None, target=do_expat)
t.start()
while True:
q_lock.acquire() <---
print(q)
q_lock.release() <---
time.sleep(1)
你知道,它非常简单,我们只是创建了一个锁变量来保护我们的对象,并且每次在我们使用对象之前获取该锁,并在每次完成对象上的任务后释放。这样,我们保证q.append(name)
永远不会与print(q)
重叠。
(对于较新版本的Python,还有一个“with ....”语法可以帮助您不要经常忘记释放锁或关闭文件或其他清理。)
答案 3 :(得分:0)
我对实现并不了解很多,但如果解析是直到完成执行的C代码,那么其他Python线程将无法运行。如果解析器正在回调Python代码,则可能会释放GIL以供其他线程运行,但我不确定。您可能想查看这些详细信息。