上篇文章没想到能用那么多篇幅,主要是我自己的小实验做的太多了,终于到了多线程环节,先来一波单线程的示例:
def sing(name):
print('演唱《%s》' % name)
sing_time = 2
sleep(sing_time)
def main():
start = time()
sing('演员')
sing('绅士')
end = time()
print('总共耗费了%d秒' % (end - start))
if __name__ == '__main__':
main()
效果不用说,顺序执行自然是耗时较长,那么我们可以用多线程来节约时间,提高程序的执行效率。
目前我了解到的线程写法有两种,刚开始我是觉得第一种优于第二种,但是仔细想一下,可能这两种写法并没有优劣之分,这个一会儿说,先看代码。
from threading import Thread
from time import time, sleep
def sing(name):
print('演唱《%s》' % name)
sing_time = 2
sleep(sing_time)
def main():
start = time()
t = Thread(target = sing, args = ('演员', ))
t2 = Thread(target = sing, args = ('绅士', ))
t.start()
t2.start()
t2.join()
end = time()
print('总共耗费了%d秒' % (end - start))
if __name__ == '__main__':
main()
第一种写法就是通过Thread方法来创建线程,start、join这些方法也算是老朋友了,上篇文章都接触过。
如果多线程执行相同的方法,这种写法是比较便捷的。
class sing(Thread):
def __init__(self, name):
super().__init__()
self._name = name
def run(self):
print('演唱《%s》' % self._name)
sing_time = 2
sleep(sing_time)
def main():
start = time()
sing('演员').start()
t = sing('绅士')
t.start()
t.join()
end = time()
print('总共耗费了%d秒' % (end - start))
if __name__ == '__main__':
main()
这是第二种写法,继承Thread类,重写run方法,这和java中继承Thread就比较类似了,run方法的内容就是线程将要执行的任务,对于多线程执行不同任务的时候,这种写法可能会更好一些。
那么线程间如何共享数据呢?提到这里就又让我想起了学java时学到的线程知识,假设我们创建了一个全局变量,它作为共享变量,每个线程对其执行++操作,那么线程会先读取它的值,写入本地内存(线程的本地内存),执行自增操作,再回写到主存中,那么当两个线程并行的时候,同时读取到同一个变量,他们读取到的值是相同的,同时自增1,得到的结果也是相同的,最终结果就可想而知了,这是一个比较简单的线程安全问题,为了避免这种问题,我们要保证线程同步。
先尝试一波不安全的线程代码:
from threading import Thread
from time import time, sleep
common = 0
def add():
global common
c = common + 10
sleep(0.02)
common = c
def main():
global common
while common == 10000 or common == 0:
common = 0
list0 = []
for x in range(1000):
t = Thread(target = add, args = ())
t.start()
list0.append(t)
for t in list0:
t.join()
print(common)
if __name__ == '__main__':
main()
假设线程每次执行+10操作耗费0.02秒,可以看到1000个线程每个线程都+10最终结果居然不等于10000。
本想就着现有的代码改良一波,想不到直接报错,查了一下,貌似是由于并发线程太多被检测到,然后直接不让执行了,行吧。
from threading import Thread, Lock
from time import time, sleep
common = 0
lock = Lock()
def add():
global common, lock
#lock.acquire()
c = common + 10
sleep(0.02)
common = c
#lock.release()
def main():
global common
common = 0
list0 = []
for x in range(100):
t = Thread(target = add, args = ())
t.start()
list0.append(t)
for t in list0:
t.join()
print(common)
if __name__ == '__main__':
main()
现在加锁的代码注掉了,依然是错误的计算结果,当正常加锁后,结果正确。
实际上释放锁的代码最好写在finally中,不然可能真正写项目的时候要死锁的。
但是,python多线程并不能发挥多核cpu的优势,原因是存在一个全局解释器锁,想执行得先获取这个锁,执行100条字节码,解释器自动释放这个锁让别的线程有机会执行,这是我现在学习的这套教程里讲到的。
那么也就是说线程不仅要获取我们自定义的线程锁,还需要获取全局解释器锁才能顺利执行100条字节码,应该是这个意思吧。所以说在python中线程并不能真正的并行?同一时刻,只有一个线程在执行?但是运用了多线程的确缩短了执行时间,看来还有一些进阶一点的东西需要以后慢慢探索。
资料中还讲到了关于效率的问题,这一部分的话,我本人没有实践经验,写出来也是照抄照搬,那就没多大意思了,所以暂且略过。
然后资料中提到了协程,也就是单线程+异步io,这玩意和单线程多路io复用说的应该是一回事吧?不是特别清楚,这一块我本人了解的比较浅薄,大佬说之后会出文章介绍,本篇文章中提到了单线程避免了线程切换和加锁的消耗,这个倒是挺好理解的哈。
这个系列写到了第四章还没有把大佬的教程推荐给大家,这一系列教程是我在微信公众号中偶然发现的,甚是喜欢,名字叫做Python-100-Days-master。
地址:https://github.com/jarodHAN/Python-100-Days-master