多线程适用于阻塞式IO场景,不适用于并行计算场景

Python的标准实现是CPython。
CPython执行Python代码分为2个步骤:首先,将文本源码解释编译为字节码,然后再用一个解释器去
解释运行字节码。字节码解释器是有状态的,需要维护该状态的一致性,因此使用了GIL(Global
Interpreter Lock,全局解释器锁)。
GIL的存在,使得CPython在执行多线程代码的时候,同一时刻只有一个线程在运行,无法利用多CPU
提高运算效率。但是这个特点也带来了一个好处:CPython运行多线程的时候,内部对象缺省就是线程
安全的。这个特性,被非常多的Python库开发者所依赖,直到CPython的开发者想要去除GIL的时候,
发现已经有大量的代码库重度依赖这个GIL带来的内部对象缺省就是线程安全的特性,变成一个无法解
决的问题了。
虽然多线程在并行计算场景下无法带来好处,但是在阻塞式IO场景下,却仍然可以起到提高效率的作
用。这是因为阻塞式IO场景下,线程在执行IO操作时并不需要占用CPU时间,此时阻塞IO的线程可以被
挂起的同时继续执行IO操作,而让出CPU时间给其他线程执行非IO操作。这样一来,多线程并行IO操作
就可以起到提高运行效率的作用了。
综上,Python的标准实现CPython,由于GIL的存在,同一个时刻只能运行一个线程,无法充分利用多
CPU提升运算效率,因此Python的多线程适用于阻塞式IO的场景,不适用于并行计算的场景。
下面举一个对计算量有要求的求一个数的因数分解的代码实例,来说明Python多线程不适用于并行计
算的场景:

# -*- coding:utf-8 -*-
from time import time
from threading import Thread
def factorize(number):
	for i in range(1, number + 1):
		if number % i == 0:
			yield i
class FactorizeThread(Thread):
	def __init__(self, number):
		Thread.__init__(self)
		self.number = number
	def run(self):
		self.factors = list(factorize(self.number))
	def test(numbers):
		start = time()
		for number in numbers:
			list(factorize(number))
		end = time()
		print('Took %.3f seconds' % (end - start))
	def test_thread(numbers):
		start = time()
		threads = []
		for number in numbers:
			thread = FactorizeThread(number)
			thread.start()
			threads.append(thread)
		for t in threads:
			t.join()
		end = time()
		print('Mutilthread Took %.3f seconds' % (end - start))
if __name__ == "__main__":
	numbers = [2139079, 1214759, 1516637, 1852285]
	test(numbers)
	test_thread(numbers)

代码输出:

Took 0.319 seconds
Mutilthread Took 0.539 seconds

以上代码运行结果只是一个参考值,具体数据跟运行环境相关。但是可以看到单线程方式比多线程方式
的计算速度要快。由于CPython运行多线程代码时因为GIL的原因导致每个时刻只有一个线程在运行,
因此多线程并行计算并不能带来时间上的收益,反而因为调度线程而导致总时间花费更长。
对于IO阻塞式场景,多线程的作用在于发生IO阻塞操作时可以调度其他线程执行非IO操作,因此在这个
场景下,多线程是可以节省时间的。可以用以下的代码来验证:

# -*- coding:utf-8 -*-
from time import time
from threading import Thread
import os
def slow_systemcall(n):
	for x in range(100):
		open("test_%s" % n, "a").write(os.urandom(10) * 100000)
def test_io(N):
	start = time()
	for _ in range(N):
		slow_systemcall(_)
	end = time()
	print('Took %.3f seconds' % (end - start))
def test_io_thread(N):
	start = time()
	threads = []
	for _ in range(N):
		thread = Thread(target=slow_systemcall, args=("t_%s"%_,))
		thread.start()
		threads.append(thread)
	for thread in threads:
		thread.join()
	end = time()
	print('Multithread Took %.3f seconds' % (end - start))
if __name__ == "__main__":
	N = 5
	test_io(N)
	test_io_thread(N)

代码输出:

Took 5.179 seconds
Multithread Took 1.451 seconds

可以看到单线程花费时间与多线程花费时间之比接近1:4,考虑线程调度的时间,这个跟一般语言的多线
程起的作用比较相似。这是因为当Python执行IO操作时,实际上是执行了系统调用,此时线程会释放
GIL,直到系统调用结束时,再申请获取GIL,也就是在IO操作期间,线程确实是并行执行的。
Python的另外一个实现JPython就没有GIL,但是它并不是最常见的Python实现。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值