我的Python多线程代码是否受Global Interpreter Lock的影响

时间:2016-09-27 15:43:34

标签: python multithreading threadpool

我正在尝试使用ThreadPool来加速我的代码。

输入是一个包含大约80000个元素的字典,每个元素都是25个元素的列表。我必须通过处理和组合每个列表中的元素为字典中的每个元素生成一个输出列表。

可以单独分析所有列表,因此该设置应该可以轻松并行化。

以下是我用于pool.map的基本设置:

from multiprocessing.dummy import Pool as ThreadPool

pool   = ThreadPool(NUM_THREADS)
output = pool.map(thread_compute, iterable_list)
pool.close()
pool.join
  • 方法1(错误):将字典定义为全局,并让每个线程获取字典的键作为输入。

    # in the main
    global input_dict
    iterable_list = input_dict.keys()
    
    # thread_compute function
    def thread_compute(key_i):
        list_to_combine = input_dict[key_i]
        # call a function
        processed_list = do_stuff_function(list_to_combine)
        return processed_list
    

我很快意识到方法1因为全局变量input_dict而无法工作,即使它从未被写入操作(因此应该是线程安全的),也受到GIL的保护({ {3}},link 1) - 尝试从单独的线程中安全地访问Python对象时的全局强制锁。

  • 方法2(不工作):我创建了一个包含与input_dict相同元素的列表,其中每个条目都是25个项目的列表。此列表不是全局变量,并且每个线程都应该能够访问元素(25项列表),而不会因GIL而产生任何开销。

    # in the main
    items = list(input_dict.items())
    iterable_list = [items[i][1] for i in range(len(items))]
    
    # making sure the content is correct
    assert(len(iterable_list) == len(input_dict))
    for i in xrange(len(input_dict.keys())):
        assert(iterable_list[i] == input_dict[input_dict.keys()[i]])
    
    # thread_compute function
    def thread_compute(list_of_25_i):
        # call a function
        processed_list = do_stuff_function(list_of_25_i)
        return processed_list
    

以下是1,2,4和16个线程的执行时间:

   1:  Done, (t=36.6810s).
   2:  Done, (t=46.5550s).
   4:  Done, (t=48.2722s).
   16: Done, (t=48.9660s).

为什么添加线程会导致时间增加?我绝对相信这个问题可以从多线程中受益,并认为添加线程的开销不能单独负责增加。

2 个答案:

答案 0 :(得分:2)

如果do_stuff_function受CPU限制,那么在多线程中运行它将无济于事,因为GIL一次只允许执行1个线程。

在Python中解决这个问题的方法是使用多个进程,只需替换

from multiprocessing.dummy import Pool

from multiprocessing import Pool

答案 1 :(得分:1)

尝试使用Pooldocs),因为它使用processes代替threads

  • Python中的线程concurrent而不是parallel,这基本上意味着解释器在执行多个线程之间切换非常快,而在执行一个线程时,锁定所有其他线程的解释器,并行运行操作的印象

  • 另一方面,进程在时间方面产生的成本要高得多,因为它们基本上是解释器的自己的实例,并且它们操作的数据必须被序列化,并从主进程发送到工作进程。在那里它被反序列化,计算,结果被序列化并再次发回。

但是,考虑到您发布的适度处理时间,生成流程所需的时间可能会对整体性能产生负面影响。