这里说一个打击人的消息,python的多线程是假的多线程
核心意思就是无论你开启几个线程,你有多少个CPU,Python解释器在执行的时候一个时刻只会执行一个线程,这就叫做GIL
WTF?可能你会问:
那还要什么多线程,再多线程也就跑一个线程算什么多线程?
为什么我使用了多线程之后程序运行消耗时间减少了,这不是有效么?
为什么我使用多线程时间没有减少,反而增加了?
下面就先解答一下这些问题
既然同一时刻只会执行一个线程,为什么还要有多线程?
这里就要分两种情况,看看你的代码是计算密集型还是IO密集型了,计算密集型顾名思义就是需要大量计算的代码,绝大部分时间都消耗在CPU计算上了,这个时候啊,不论你开多少线程,他用的时间都是这么多,甚至比原来时间还长,因为全局解释器锁(GIL)一个时刻只让你跑一个线程啊,大部分计算密集型任务你分了很多线程但是依然会按照代码顺序线性执行,甚至因为代码变得亢长了,反而使执行时间增加了
但是IO密集型就不一样了,90%以上的时间都花费在网络、硬盘、输入输出上了,CPU执行完命令之后剩下的就不需要在CPU中跑了,就可以释放内存来跑下一条命令了,所以对于IO密集型任务,多线程还是会起到很大的作用的,所以说有的使用了多线程之后还是能大大提高效率的
上面也加了一些自己的理解,如果你想更深入的理解,就需要接触一些更低层的东西,这些这里就暂时不做研究下面就看一个简单的多线程案例
import threading
import time
def express1(n):
print('this is', n)
time.sleep(2)
def express2(m):
print('this is', m)
time.sleep(2)
# s1 = threading.Thread(target=express1, args=('one', ))
# s2 = threading.Thread(target=express2, args=('two', ))
# s1.start()
# s2.start()
express1('one')
express2('two')
这段代码先打印了‘this is one’,然后间隔两秒又打印了‘this is two’再等待两秒程序结束,可以看到这是线性执行的,整个程序花费了四秒左右,接下来看看如何实现并发
import threading
import time
def express1(n):
print('this is', n)
time.sleep(2)
def express2(m):
print('this is', m)
time.sleep(2)
s1 = threading.Thread(target=express1, args=('one', ))
s2 = threading.Thread(target=express2, args=('two', ))
s1.start()
s2.start()
# express1('one')
# express2('two')
这样,就会同时打印‘this is one’和‘this is two’,整个程序花费了两秒左右,可以很明显的看出来express1和 express2是一起执行的,这样就实现了多线程
上面是一种实现方式,threadingmodule还提供了另一种实现方式:
import threading
import time
# 继承类threading.Thread
class MyThread(threading.Thread):
def __init__(self, n):
# 这里要继承构造函数
super(MyThread, self).__init__()
# 可以定义自己的实例变量
self.n = n
def run(self):
print('running task', self.n)
time.sleep(2)
t1 = MyThread('t1')
t2 = MyThread('t2')
t1.start()
t2.start()
这样就实现了通过继承父类的方式使用多线程,这里注意的几个点:
1.继承类之后还要继承构造函数
2.这里的run函数是重写的,所以说t1.start()之后才会自动调用run函数,具体详细细节可以自行查看源码
这样开一条线程就要写一行代码,我要是开50、100个线程不是要写很多行代码,当然不用,这里我觉得应该没什么难度,写个小循环不就行了
import threading
import time
# 继承类threading.Thread
class MyThread(threading.Thread):
def __init__(self, n):
# 这里要继承构造函数
super(MyThread, self).__init__()
# 可以定义自己的实例变量
self.n = n
def run(self):
print('running task', self.n)
time.sleep(2)
# t1 = MyThread('t1')
# t2 = MyThread('t2')
for i in range(50):
t = MyThread(i)
t.start()
# t1.start()
# t2.start()
那我们能利用timemodule计算一下这50个线程花费了多长时间么?肯定也是可以的,但是,这里有一些问题需要注意,先说原理,明白发生了什么就知道了问题所在和解决办法
我们开启了50个线程,但是这个程序不单有50个线程,还有一个主线程,假如我们在循环前后各加入一句获得当前时间的代码,那么这两句代码就是在主线程里,然后主线程跑主线程的,子线程跑子线程的,因为子线程需要的时间更长,所以主线程先跑完,也就是说计算得出的时间并不是这些子线程跑完的时间,主线程不会等待子线程跑完自己才去跑,而是大家一起跑,都跑完程序就结束
那我们如何计算这些线程执行所花费的时间呢?这个时候就有一个方法
join()
能够使主线程等待子线程的执行完毕才会继续主线程,下面看一段代码
import threading
import time
# 继承类threading.Thread
class MyThread(threading.Thread):
def __init__(self, n):
# 这里要继承构造函数
super(MyThread, self).__init__()
# 可以定义自己的实例变量
self.n = n
def run(self):
print('running task', self.n)
time.sleep(2)
# 得到开始时间
start_time = time.time()
# 声明空列表
threads = []
# 循环开启50线程
for i in range(50):
t = MyThread(i)
t.start()
# 线程实例放入列表中
threads.append(t)
# 对线程实例所在列表再循环
for res in threads:
# 对每一个实例使用jion()方法
res.join()
# 获得结束时间
end_time = time.time()
# 计算花费时间
spend_time = start_time - end_time
# 打印花费时间
print(spend_time)
这里要注意的点就是join()方法不能在线程启动之后就使用,这样的话50个线程就会变成串行,必须要等50个线程全部启动之后才能使用join()方法,这里就用到了一个小技巧新建一个空列表,再将50个线程实例放进去,这样后面就可以实现对这50个线程使用join()方法了
最后,我们就可以得到我们想要的50个线程执行所花费的时间