我正在尝试使用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).
为什么添加线程会导致时间增加?我绝对相信这个问题可以从多线程中受益,并认为添加线程的开销不能单独负责增加。
答案 0 :(得分:2)
如果do_stuff_function
受CPU限制,那么在多线程中运行它将无济于事,因为GIL一次只允许执行1个线程。
在Python中解决这个问题的方法是使用多个进程,只需替换
from multiprocessing.dummy import Pool
与
from multiprocessing import Pool
答案 1 :(得分:1)
尝试使用Pool
(docs),因为它使用processes
代替threads
。
Python中的线程concurrent
而不是parallel
,这基本上意味着解释器在执行多个线程之间切换非常快,而在执行一个线程时,锁定所有其他线程的解释器,并行运行操作的印象。
另一方面,进程在时间方面产生的成本要高得多,因为它们基本上是解释器的自己的实例,并且它们操作的数据必须被序列化,并从主进程发送到工作进程。在那里它被反序列化,计算,结果被序列化并再次发回。
但是,考虑到您发布的适度处理时间,生成流程所需的时间可能会对整体性能产生负面影响。