线程

Python多线程编程(一):threading 模块 Thread 类的用法详解

我们进行程序开发的时候,肯定避免不了要处理并发的情况。

一般并发的手段有采用多进程和多线程。

但线程比进程更轻量化,系统开销一般也更低,所以大家更倾向于用多线程的方式处理并发的情况。

Python 提供多线程编程的方式。

本文基于 Python3 讲解,Python 实现多线程编程需要借助于 threading 模块。

所以,我们要在代码中引用它。

import threading

threading 模块中最核心的内容是 Thread 这个类。

我们要创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。
值得注意的是,程序运行时默认就是在主线程上
创建 Thread 对象有 2 种手段。

  1. 直接创建 Thread ,将一个 callable 对象从类的构造器传递进去,这个 callable 就是回调函数,用来处理任务。
  2. 编写一个自定义类继承 Thread,然后复写 run() 方法,在 run() 方法中编写任务处理代码,然后创建这个 Thread 的子类。

1. 直接创建 Thread 对象。

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

Thread 的构造方法中,最重要的参数是 target,所以我们需要将一个 callable 对象赋值给它,线程才能正常运行。

如果要让一个 Thread 对象启动,调用它的 start() 方法就好了。

下面是代码示例。

import threading
import time

def test():

    for i in range(5):
        print('test ',i)
        time.sleep(1)


thread = threading.Thread(target=test)
thread.start()

for i in range(5):
    print('main ', i)
    time.sleep(1)

上面代码很简单,在主线程上打印 5 次,在一个子线程上打印 5 次。

运行结果如下:

test  0
main  0
main  1
test  1
main  2
test  2
main  3
test  3
main  4
test  4

上面的 callable 没有参数,如果需要传递参数的话,args 是固定参数,kwargs 是可变参数。

Thread 的名字

每一个 Thread 都有一个 name 的属性,代表的就是线程的名字,这个可以在构造方法中赋值。

如果在构造方法中没有个 name 赋值的话,默认就是 “Thread-N” 的形式,N 是数字。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(1)


thread = threading.Thread(target=test)
thread.start()

for i in range(5):
    print(threading.current_thread().name+' main ', i)
    time.sleep(1)

通过 thread.current_thread() 方法可以返回线程本身,然后就可以访问它的 name 属性。

上面代码运行结果如下:

Thread-1 test  0
MainThread main  0
Thread-1 test  1
MainThread main  1
Thread-1 test  2
MainThread main  2
Thread-1 test  3
MainThread main  3
Thread-1 test  4
MainThread main  4

如果我们在 Thread 对象创建时,构造方法里面赋值。

thread = threading.Thread(target=test,name='TestThread')

那么,运行结果会变成这个样子。

TestThread test  0
MainThread main  0
MainThread main  1
TestThread test  1
MainThread main  2
TestThread test  2
MainThread main  3
TestThread test  3
MainThread main  4
TestThread test  4

Thread 的生命周期

创建对象时,代表 Thread 内部被初始化。
调用 start() 方法后,thread 会开始运行。
thread 代码正常运行结束或者是遇到异常,线程会终止。

可以通过 Thread 的 is_alive() 方法查询线程是否还在运行。

值得注意的是,is_alive() 返回 True 的情况是 Thread 对象被正常初始化,start() 方法被调用,然后线程的代码还在正常运行。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(0.5)


thread = threading.Thread(target=test,name='TestThread')
# thread = threading.Thread(target=test)
thread.start()

for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

在上面的代码中,我让 TestThread 比 MainThread 早一点结束,代码运行结果如下。

TestThread test  0
MainThread main  0
TestThread is alive  True
TestThread test  1
MainThread main  1
TestThread is alive  True
TestThread test  2
TestThread test  3
MainThread main  2
TestThread is alive  True
TestThread test  4
MainThread main  3
TestThread is alive  False
MainThread main  4
TestThread is alive  False

我们可以看到,主线程通过调用 TestThread 的 isAlive() 方法,准确查询到了它的存货状态。

join() 提供线程阻塞手段。

上面代码两个线程是同时运行的,但如果让一个先运行,一个后运行,怎么做呢?
调用一个 Thread 的 join() 方法,可以阻塞自身所在的线程。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(0.5)


thread = threading.Thread(target=test,name='TestThread')
thread.start()
thread.join()

for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

主线程创建了 TestThread 对象后,让其 start,然后通过调用 join() 方法,实现等待。程序运行结果如下:

TestThread test  0
TestThread test  1
TestThread test  2
TestThread test  3
TestThread test  4
MainThread main  0
TestThread is alive  False
MainThread main  1
TestThread is alive  False
MainThread main  2
TestThread is alive  False
MainThread main  3
TestThread is alive  False
MainThread main  4
TestThread is alive  False

默认的情况是,join() 会一直等待对应线程的结束,但可以通过参数赋值,等待规定的时间就好了。

def join(self, timeout=None):

timeout 是一个浮点参数,单位是秒。

如果我们更改上面的代码。

thread.join(1.0)

它的结果会是这样。

TestThread test  0
TestThread test  1
MainThread main  0
TestThread is alive  True
TestThread test  2
TestThread test  3
MainThread main  1
TestThread is alive  True
TestThread test  4
MainThread main  2
TestThread is alive  False
MainThread main  3
TestThread is alive  False
MainThread main  4
TestThread is alive  False


主线程只等待了 1 秒钟。
Thread 中的 daemon 属性

有同学可能会注意到,Thread 的构造方法中有一个 daemon 参数。默认是 None。

那么,daemon 起什么作用呢?

我们先看一段示例代码。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(2)


thread = threading.Thread(target=test,name='TestThread')
# thread = threading.Thread(target=test,name='TestThread',daemon=True)
thread.start()


for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

我们让主线程执行代码的时长比 TestThread 要短。

程序运行结果如下。

TestThread test  0
MainThread main  0
TestThread is alive  True
MainThread main  1
TestThread is alive  True
TestThread test  1
MainThread main  2
TestThread is alive  True
MainThread main  3
TestThread is alive  True
TestThread test  2
MainThread main  4
TestThread is alive  True
TestThread test  3
TestThread test  4

MainThread 没有代码运行的时候,TestThread 还在运行。

这是因为 MainThread 在等待其他线程的结束。

TestThread 中 daemon 属性默认是 False,这使得 MainThread 需要等待它的结束,自身才结束。

如果要达到,MainThread 结束,子线程也立马结束,怎么做呢?

其实很简单,只需要在子线程调用 start() 方法之前设置 daemon 就好了。

当然也可以在子线程的构造器中传递 daemon 的值为 True。

thread = threading.Thread(target=test,name='TestThread',daemon=True)
# thread.setDaemon(True)

更改前面代码示例,运行结果如下

TestThread test  0
MainThread main  0
TestThread is alive  True
MainThread main  1
TestThread is alive  True
TestThread test  1
MainThread main  2
TestThread is alive  True
MainThread main  3
TestThread is alive  True
TestThread test  2
MainThread main  4
TestThread is alive  True

可以看到 MainThread 结束了 TestThread 也结束了。

2.自定义类继承 Thread

前面讲过,直接初始化一个 Thread,然后,现在还有一种方式就是自定义一个 Thread 的子类,然后复写它的 run() 方法。

import threading
import time


class TestThread(threading.Thread):

    def __init__(self,name=None):
        threading.Thread.__init__(self,name=name)

    def run(self):
        for i in range(5):
            print(threading.current_thread().name + ' test ', i)
            time.sleep(1)


thread = TestThread(name='TestThread')
thread.start()


for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

上面的代码,我们自定义了 TestThread 这个类,然后继承了 threading.Thread。

只有在 run() 方法中处理逻辑。最终代码运行结果如下:

TestThread test  0
MainThread main  0
TestThread is alive  True
TestThread test  1
MainThread main  1
TestThread is alive  True
TestThread test  2
MainThread main  2
TestThread is alive  True
MainThread main  3
TestThread is alive  True
TestThread test  3
MainThread main  4
TestThread test  4
TestThread is alive  True

这与之前的效果并无差异,但我还是推荐用这种方法,毕竟面向对象编程嘛。

自此,Python 多线程编码技术就大致介绍完毕,大家可以进行实际代码编写了。

但是,多线程编程的难点在于多个线程之间共享数据的同步,这是非常容易出错的地方,我将分别编写相应的博文去介绍一些高级的技术点。

转自:https://blog.csdn.net/briblue/article/details/85101144

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值