跟踪线程访问文件,有和没有队列

时间:2014-10-13 01:31:52

标签: python multithreading

我在使用线程和队列时发现了一些我很难理解的行为。我花了很多时间去搜索谷歌,但仍然找不到我理解的解释,所以我希望有人可以帮助我。

我有这样的功能......

# 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}

我其实有几个问题......

  1. 为什么在检查线程时添加time.sleep(1)会使stat_somedir()运行得更快?我认为它与thread.isAlive有关,阻止线程运行os.path.isfile,但我不确定,我无法解释原因。 (我创建了另一个版本,而不是os.path.isfile,只是打印出数字,但是time.sleep没有区别;代码在线程外部,线程内部和队列中运行同样快。那个&#39 ;为什么我认为文件访问的一些原因是解释......)

  2. 为什么在队列中运行此代码对于在posix.stat上添加time.sleep(1)所花费的时间具有相同的效果? Queue正在做什么"引擎盖"?

  3. 非常感谢任何帮助!

1 个答案:

答案 0 :(得分:0)

分析器对你说谎。这就是原因:

在没有sleep()的情况下,当您进行系统调用以执行I / O时,正在运行的线程将进入休眠状态,直到I / O完成。与此同时,您的主线程(具有while循环的线程)在紧密的Python循环中旋转。虽然它旋转它拥有Python全局解释器锁。这意味着即使I / O完成,也不能在StatThread中运行Python代码,因为无法获取GIL。

鉴于这一切,我不确定它是否能保证你的程序能够完成 - 这可能会导致一个"活锁"取决于解释器的实现。但它最终会完成,也许是因为有一些时间量已经过去并使Python决定停止运行主旋转循环并尝试在StatThread中运行代码。

在任何情况下,cProfile都不知道posix.stat缺乏执行时间,它只知道从开始到完成的时间很长。它不会减去等待GIL所花费的时间。

顺便说一下,如果你愿意的话,你可以让sleep()真的很短,它会解决问题。问题就像睡了一整秒。但最好只是join()线程,并避免任何时候烧掉CPU周期。