深入浅出Python多线程(1)创建线程
图片来源于网络
Thread对象
Thread对象位于Python标准库中的threading.py中。
它的初始化方法是这样的:
def __init__(self,group=None,target=None,name=None,
args=(),kwargs=None,*,daemon=None)
一个线程通过调用其start()方法来激活,每个线程对象只能调用一次start(),否则会抛出RuntimeError错误。
假设我们现在有一场跑步比赛,共有5名运动员参加。发令枪一响,大家肯定同时开始跑(并发),直到所有的运动员跑完,比赛结束。在这个例子里面,我们可以把赛场看成是一个进程,而赛场里面的每个运动员占用一条跑道,这个是线程。发令枪一响,所有线程启动,直到运行结束。
让我们模拟一下上述场景,我们可以用两种方式来实现。为了模拟跑步,我们引入一个random来产生一个随机事件,然后使用time.sleep模拟跑步用时,尽量使得结果看起来像那么回事:)
使用函数创建线程
import time
import random
import threading
def runner(name):
time_cost = random.randint(7, 12)
print("{}: 我开始跑了".format(name))
time.sleep(time_cost)
print("{}: 我跑完了,用时{}秒".format(name, time_cost))
if __name__ == '__main__':
# 我们有5个运动员,创建5个线程,分别调用start方法激活线程
name_list = ['张三', '李四', '王五', '赵六', '何二']
for name in name_list:
t = threading.Thread(target=runner, args=(name,))
t.start()
在这个例子中:
- 我们定义了一个
runner
函数,来模拟跑步的行为。 - 我们通过实例化
threading.Thread
类来创建线程。 - 在这个例子中,由于
runner
函数是一个带参数的函数,我们还通过args
把参数传给了runner
- 最后分别调用线程对象的
start
方法,来开启线程。
# 运行结果
张三: 我开始跑了
李四: 我开始跑了
王五: 我开始跑了
赵六: 我开始跑了
何二: 我开始跑了
李四: 我跑完了,用时7秒
赵六: 我跑完了,用时8秒
何二: 我跑完了,用时8秒
张三: 我跑完了,用时11秒
王五: 我跑完了,用时11秒
我们发现,几乎第一时间,5个人都打印了我开始跑了
,最后,李四得了第一名。
通过继承Thread类来创建线程
当然,我们也可以编写面向对象的代码,通过继承Thread类来实现上面的功能。
import time
import random
import threading
class Runner(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
def run(self):
time_cost = random.randint(7, 12)
print("{}: 我开始跑了".format(self.name))
time.sleep(time_cost)
print("{}: 我跑完了,用时{}秒".format(self.name, time_cost))
if __name__ == '__main__':
# 我们有5个运动员,实例化5个Runner对象,分别调用start方法激活线程
name_list = ['张三', '李四', '王五', '赵六', '何二']
for name in name_list:
t = Runner(name)
t.start()
在这个例子中:
- 我们创建了一个类
Runner
,让它继承自threading.Thread
。 - 在构造器中我们使用
super()
来调用父类的构造器,并且把name
传给他。 - 继承
Thread
来实现多线程最重要的是override
父类的run
方法,我们把跑步的逻辑放在run
方法里面。 - 最后跟上个例子的逻辑类似,只是我们通过实例化
Runner
类来创建了线程。
以上就是Python创建线程的两种方式。
两种方式如何选择
- 继承Thread类的方式虽然可行,但是这样的实现方式是依赖threading库的,代码只能在线程上下文中使用。
- 而第一种方式,实现的代码可以脱离线程上下文,可以在其他上下文使用。
- 如果业务逻辑简单,推荐第一种方式(使用函数创建线程)。
- 如果业务相对复杂,但只跟线程相关,推荐第二种方式(通过继承Thread类来创建线程)。
- 如果逻辑相对复杂,又不是纯线程业务的,可以实现一个类,还是以实例化Thread类,target传参数的方式去做,比如:
class Runner(object):
def __init__(self, name):
self.name = name
def start(self):
t = threading.Thread(target=self.run)
t.start()
def run(self):
# 线程逻辑
pass
...
节外生枝
在我们上面的例子中,我们很好的实现了模拟赛跑的一个效果。但是别高兴的太早,这时候主办方(产品经理)找过来了,说你程序是跑完了,但是还没有宣布比赛结束,最后你得打印出一行比赛结束
才行。
这还不简单,不就是最后加一个打印吗?
...
if __name__ == '__main__':
# 我们有5个运动员,实例化5个Runner对象,分别调用start方法激活线程
name_list = ['张三', '李四', '王五', '赵六', '何二']
for name in name_list:
t = Runner(name)
t.start()
print('比赛结束')
我们来看看结果:
张三: 我开始跑了
李四: 我开始跑了
王五: 我开始跑了
赵六: 我开始跑了
何二: 我开始跑了
比赛结束
王五: 我跑完了,用时8秒
张三: 我跑完了,用时9秒
李四: 我跑完了,用时9秒
何二: 我跑完了,用时9秒
赵六: 我跑完了,用时12秒
WTF,大写的黑人问号,还没跑完就打印啦,这不科学啊。
下一小节,让我们来试着分析并解决这个不同寻常的问题吧。
版权声明
© 著作权归作者所有
允许自由转载,但请保持署名和原文链接。 不允许商业用途、盈利行为及衍生盈利行为。
很受用 谢谢