一文速通Python并行计算:02 Python多线程编程-threading模块、线程的创建和查询与守护线程

106 阅读8分钟

image

1.Python threading 模块

Python3 实现多线程编程需要借助于 threading 模块,threading 是 Python 标准库中的一个模块,它提供了一个高级的面向对象的线程编程接口。使用 threading 模块可以更方便地创建和管理线程,包括线程同步、线程通信、线程优先级等功能。(在 Python2 中,也有 thread 模块,它提供了一些基本的线程操作函数,例如 start_new_thread()函数用于创建新线程,exit()函数用于退出线程等。thread 模块只能在 Python 2 中使用。)

threading 模块包括以下组件:

  • (1)Thread 线程类, 这是我们用的最多的一个类,你可以指定线程函数执行或者继承自它都可以实现子线程功能;
  • (2)Timer 与 Thread 类似, 但要等待一段时间后才开始运行;
  • (3)Lock 锁, 这个我们可以对全局变量互斥时使用;
  • (4)RLock 可重入锁, 使单线程可以再次获得已经获得的锁;
  • (5)Condition 条件变量, 能让一个线程停下来,等待其他线程满足某个“条件”;
  • (6)Event 通用的条件变量。 多个线程可以等待某个事件发生,在事件发生后,所有的线程都被激活;
  • 7)Semaphore 为等待锁的线程提供一个类似“等候室”的结构;
  • (8)BoundedSemaphore 与 semaphore 类似,但不允许超过初始值;
  • (9)Queue: 实现了多生产者(Producer)、多消费者(Consumer)的队列,支持锁原语,能够在多个线程之间提供很好的同步支持。

2.线程创建

使用线程最简单的一个方法是,用一个目标函数实例化一个 Thread 然后调用 start() 方法启动它。Python 的 threading 模块提供了 Thread() 方法在不同的线程中运行函数或处理过程等。

Thread 类代表一个在独立控制线程中运行的活动。该类提供的函数包括:

一般来说,新建线程有两种模式,一种是创建线程要执行的函数,把这个函数传递进 Thread 对象里,让它来执行;另一种是直接从 Thread 继承,创建一个新的 class,把线程执行的代码放到这个新的 class 里。

2.1 调用 Thread 类的构造器创建线程

Thread 类提供了如下的 __init__() 构造器,可以用来创建线程:

__init__(self, group=None, target=None, name=None, args=(), kwargs=None, *,daemon=None)

此构造方法中,以上所有参数都是可选参数,即可以使用,也可以忽略。其中各个参数的含义如下:

  • group:指定所创建的线程隶属于哪个线程组;
  • target:当线程启动的时候要执行的函数;
  • name: 线程的名字,默认会分配一个唯一名字 Thread-N;
  • args:以元组的方式,为 target 指定的方法传递参数;
  • kwargs:以字典的方式,为 target 指定的方法传递参数;
  • daemon:指定所创建的线程是否为守护线程。

下面程序演示了如何使用 Thread 类的构造方法创建一个线程:

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 次。

如下为代码输出,可以看到主线程和子线程交替执行:

image

2.2 继承 Thread 类创建线程类

通过继承 Thread 类,我们可以自定义一个线程类,从而实例化该类对象,获得子线程。需要注意的是,在创建 Thread 类的子类时,必须重写从父类继承得到的 run()方法。

import threading
 
_#创建子线程类,继承自 Thread 类_
class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self);
    _# 重写run()方法_
    def run(self):
        print("I am %s" % self.name)
 
if __name__ == "__main__":
    for thread in range(0, 5):
        t = MyThread()
        t.start()

image

这里,线程启动有 start() 和 join() 两种方法。用 start() 方法来启动线程,真正实现了多线程运行,这时无需等待 run 方法体代码执行完毕而直接继续执行后面的代码。

通过调用 Thread 类的 start() 方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到 cpu 时间片,就开始执行 run() 方法;join() 让调用它的线程一直等待直到执行结束(即阻塞调用它的主线程, t 子线程执行结束,主线程才会继续执行)。

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到 join()方法了。

这里,我们看一下使用 join() 方法启动线程:

import threading
_#定义线程要调用的方法,*add可接收多个以非关键字方式传入的参数_
def action(*add):
    for arc in add:
        _#调用 getName() 方法获取当前执行该程序的线程名_
        print(threading.current_thread().name +" "+ arc)
_#定义为线程方法传入的参数_
my_tuple = ("http://c.biancheng.net/python/",\
            "http://c.biancheng.net/shell/",\
            "http://c.biancheng.net/java/")
_#创建线程_
thread = threading.Thread(target = action,args =my_tuple)
_#启动线程_
thread.start()
_#主线程执行如下语句_
for i in range(5):
    print(threading.current_thread().name)

程序执行结果为(不唯一):

image

可以看到,我们用 Thread 类创建了一个线程(线程名为 Thread-1),其任务是执行 action() 函数。同时,我们也给主线程 MainThread 安排了循环任务(第 16、17 行)。通过前面的学习我们知道,主线程 MainThread 和子线程 Thread-1 会轮流获得 CPU 资源,因此该程序的输出结果才会向上面显示的这样。

但是,如果我们想让 Thread-1 子线程先执行,然后再让 MainThread 执行第 16、17 行代码,该如何实现呢?很简单,通过调用线程对象的 join() 方法即可。

join() 方法的功能是在程序指定位置,优先让该方法的调用者使用 CPU 资源。该方法的语法格式如下:thread.join( [timeout] )

其中,thread 为 Thread 类或其子类的实例化对象;timeout 参数作为可选参数,其功能是指定 thread 线程最多可以霸占 CPU 资源的时间(以秒为单位),如果省略,则默认直到 thread 执行结束(进入死亡状态)才释放 CPU 资源。

3.确定当前的线程

每一个 Thread 都有一个 name 的属性,代表的就是线程的名字,这个可以在构造方法中赋值。如果在构造方法中没有个 name 赋值的话,默认就是 “Thread-N” 的形式,N 是数字。通过 thread.current_thread() 方法可以返回线程本身,然后就可以访问它的 name 属性。

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)

image

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

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

image

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.is_alive())
    time.sleep(1)

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

image

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

5.守护线程的创建

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.is_alive())
    time.sleep(1)

我们让主线程执行代码的时长比 TestThread 要短,程序运行结果如下:

image

MainThread 没有代码运行的时候,TestThread 还在运行。这是因为 MainThread 在等待其他线程的结束。TestThread 中 daemon 属性默认是 False,这使得 MainThread 需要等待它的结束,自身才结束。

如果要达到,MainThread 结束,子线程也立马结束,怎么做呢?其实很简单,只需要在子线程调用 start() 方法之前设置 daemon 就好了。当然也可以在子线程的构造器中传递 daemon 的值为 True。

修改

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

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

image

可以看到 MainThread 结束了 TestThread 也结束了。也可以用 setDaemon 方法使得只要主线程完成了,不管子线程是否完成,都要和主线程一起退出。

文章转载自: FreakStudio

原文链接: www.cnblogs.com/FreakEmbedd…

体验地址: www.jnpfsoft.com/?from=001YH