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