我在使用线程和队列时发现了一些我很难理解的行为。我花了很多时间去搜索谷歌,但仍然找不到我理解的解释,所以我希望有人可以帮助我。
我有这样的功能......
# it doesn't really matter what the path is, so let's just refer to
# it as 'base_path'
def stat_somedir(base_path):
stat_profile = cProfile.Profile()
stat_profile.enable()
for root, dirs, files in os.walk(base_path):
for file_ in files:
file_path = os.path.join(base_path, file_)
if not os.path.isfile(file_path): continue
stat_profile.disable()
stat_profile.print_stats(sort='time')
当我通过简单地调用stat_somedir(base_path)运行该函数时,这是探查器输出的第一行。它大部分时间都花在posix.stat上......
ncalls tottime percall cumtime percall filename:lineno(function)
1610 0.008 0.000 0.008 0.000 {posix.stat}
接下来,我在一个线程中运行该函数,这里是代码......
class StatThread(Thread):
def run(self):
stat_somedir()
# initially, I thought having multiple threads might be the culprit
# so the code is structured that way, but we'll only use one thread
# in this example
thread_one = StatThread()
waiting_threads = [thread_one]
running_threads = []
for thread in waiting_threads:
running_threads.append(thread)
thread.start()
done_threads = []
while running_threads:
for thread in running_threads:
# This is commented out on purpose, it will matter later.
#time.sleep(1)
if thread.isAlive():
continue
else:
running_threads.remove(thread)
done_threads.append(thread)
探查器输出的第一行是......
ncalls tottime percall cumtime percall filename:lineno(function)
1610 3.387 0.002 3.387 0.002 {posix.stat}
显然,在posix.stat上花费的时间已经过去了。如果我注释掉睡眠线,则第一行输出变为......
ncalls tottime percall cumtime percall filename:lineno(function)
1610 0.007 0.000 0.007 0.000 {posix.stat}
这更接近于在线程外运行stat_somedir()所花费的时间。
我也尝试使用队列,这里是代码......
def do_stuff(q):
while True:
item = q.get()
stat_somedir()
q.task_done()
q = Queue.Queue(maxsize=1)
worker = Thread(target=do_stuff, args=(q,))
worker.setDaemon(True)
worker.start()
for x in range(1):
q.put(x)
q.join()
再次,这是探查器输出的第一行。这基本上相当于在线程外运行stat_somedir()或使用线程运行它并使用time.sleep()..
ncalls tottime percall cumtime percall filename:lineno(function)
1610 0.008 0.000 0.008 0.000 {posix.stat}
我其实有几个问题......
为什么在检查线程时添加time.sleep(1)会使stat_somedir()运行得更快?我认为它与thread.isAlive有关,阻止线程运行os.path.isfile,但我不确定,我无法解释原因。 (我创建了另一个版本,而不是os.path.isfile,只是打印出数字,但是time.sleep没有区别;代码在线程外部,线程内部和队列中运行同样快。那个&#39 ;为什么我认为文件访问的一些原因是解释......)
为什么在队列中运行此代码对于在posix.stat上添加time.sleep(1)所花费的时间具有相同的效果? Queue正在做什么"引擎盖"?
非常感谢任何帮助!
答案 0 :(得分:0)
分析器对你说谎。这就是原因:
在没有sleep()
的情况下,当您进行系统调用以执行I / O时,正在运行的线程将进入休眠状态,直到I / O完成。与此同时,您的主线程(具有while
循环的线程)在紧密的Python循环中旋转。虽然它旋转它拥有Python全局解释器锁。这意味着即使I / O完成,也不能在StatThread
中运行Python代码,因为无法获取GIL。
鉴于这一切,我不确定它是否能保证你的程序能够完成 - 这可能会导致一个"活锁"取决于解释器的实现。但它最终会完成,也许是因为有一些时间量已经过去并使Python决定停止运行主旋转循环并尝试在StatThread
中运行代码。
在任何情况下,cProfile
都不知道posix.stat
缺乏执行时间,它只知道从开始到完成的时间很长。它不会减去等待GIL所花费的时间。
顺便说一下,如果你愿意的话,你可以让sleep()
真的很短,它会解决问题。问题就像睡了一整秒。但最好只是join()
线程,并避免任何时候烧掉CPU周期。