深入浅出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()

在这个例子中:

  1. 我们定义了一个runner函数,来模拟跑步的行为。
  2. 我们通过实例化threading.Thread类来创建线程。
  3. 在这个例子中,由于runner函数是一个带参数的函数,我们还通过args把参数传给了runner
  4. 最后分别调用线程对象的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()

在这个例子中:

  1. 我们创建了一个类Runner,让它继承自threading.Thread
  2. 在构造器中我们使用super()来调用父类的构造器,并且把name传给他。
  3. 继承Thread来实现多线程最重要的是override父类的run方法,我们把跑步的逻辑放在run方法里面。
  4. 最后跟上个例子的逻辑类似,只是我们通过实例化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,大写的黑人问号,还没跑完就打印啦,这不科学啊。

下一小节,让我们来试着分析并解决这个不同寻常的问题吧。

版权声明

© 著作权归作者所有
允许自由转载,但请保持署名和原文链接。 不允许商业用途、盈利行为及衍生盈利行为。

标签: none

仅有一条评论

  1. 加气加油 加气加油

    很受用 谢谢

添加新评论