您的位置:js12345金沙官网登入 > 网络编程 > 多线程编程(五) threading模块

多线程编程(五) threading模块

2019-10-02 10:00
  • 背景/简介
  • 线程和进程
  • 线程和Python
  • thread 模块
  • threading模块
  • 单线程和多线程对比
  • 多线程实践
  • 生产者-消费者问题和Queue/queue 模块
  • 线程的替代方案

上一篇文章讲了python多线程的基础知识和thread模块,这一篇着重讲解一下threading模块

现在介绍更高级别的threading模块。除了Thread类之外,该模块还包括许多非常好用的同步机制。

threading模块

threading模块除了Thread类之外,好包括其他很多的同步机制,下面来看一下threading模块汇总所包含的对象。

对象 描述
Thread 执行线程的对象
Lock 锁对象
RLock 递归锁,是一个线程可以再次拥有已持有的锁对象
Condition 条件变量对象,使一个线程等待另一个线程满足特定的条件触发
Event 事件对象,普通版的Condition
Semaphore 信号量,为线程间共享的资源提供一个“计数器”,计数开始值为设置的值,默认为1
BoundedSemaphore 与Semaphore相同,有边界,不能超过设置的值
Timer 定时运行的线程对象,定时器
Barrier 界限,当达到某一界限后才可以继续执行

看到threading有这么多对象,是不是有些懵了,下面一个个的来看一下

对象 描述
Thread 表示一个执行线程的对象
Lock 锁原语对象(和 thread 模块中的锁一样)
Codition 条件变量对象,使得一个线程等待另一个线程满足特定的 “条件” ,比如改变状态或某个数据值
Event 条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有的线程将被激活
Semapore 为线程间共享的有限资源提供一个“计数器”,如果没有可用的资源时会被阻塞
BoundSemapore 与Semapore 相似,不过它不允许超过初始值
Timer 与Thread相似,不过它要在运行前等待一段时间
Barrier 创造一个“障碍” , 必须到达指定数量的线程后才可以继续
Thread类

Thread类是threading模块的主要主要执行对象。Thread对象有三个数据属性,name(线程名)、ident(线程的标识)、daemon(布尔值,是否是守护线程)。这三个数据属性可以直接通过对象进行调用并进行设置。

守护线程一般是一个等待客户端请求服务的服务器,进程退出时,该线程在正常情况下不会退出
Thread类还有一些对象方法

对象方法 描述
__init__() 实例化一个线程对象
start() 开始执行线程
run() 定义线程功能方法(一般在子类中进行重写)
join(timeout=None) 直至启动的线程终止或timeout秒,否则一直挂起,多用于主线程进行阻塞等待子线程运行完毕。
isAlivel/is_alive() 线程是否存活

注意
__init__()完整函数如下__init__(group=None,target=None,name=None,args=(),kwargs={},verbose=None,daemon=None),Thread对象实例化需要一个可调用的target(可以是一个函数,也可是一个可调用的类实例),参数args或者kwargs。

说了这么多,那怎么创建线程呢?一般有两种方法:

  • 创建Thread实例,传给其一个函数或可调用的类实例
  • 派生Thread的子类,并创建子类的实例
    一般来说,创建Thread实例并传递一个函数和派生Thread子类比较常用,后者更符合面向对象且比较容易扩展
  1. 创建Thread实例,传给它一个函数
import random
import threading
from time import ctime,sleep

def loop(nloop,nsec):
    print('start loop ',nloop,' sec:',nsec,' at:',ctime())
    sleep(nsec)
    print('end loop ',nloop,' done at:',ctime())

def main():
    print('starting at:',ctime())
    threads = []

    for i in range(3):
        t = threading.Thread(target=loop,args=(i,random.randint(1,5)))
        threads.append(t)

    for i in range(3):
        threads[i].start()

    for i in range(3):
        threads[i].join()

    print('all done at:',ctime())

if __name__ == '__main__':
    main()

传递给Thread实例一个函数其实和thread模块中差不多,这里随机生成3个Thread实例,分别运行随机事件,然后通过循环让线程启动threads[i].start(),然后通过join()让主线程等待结束。
打印结果如下

starting at: Thu Sep  7 17:53:44 2017
start loop  0  sec: 1  at: Thu Sep  7 17:53:44 2017
start loop  1  sec: 5  at: Thu Sep  7 17:53:44 2017
start loop  2  sec: 3  at: Thu Sep  7 17:53:44 2017
end loop  0  done at: Thu Sep  7 17:53:45 2017
end loop  2  done at: Thu Sep  7 17:53:47 2017
end loop  1  done at: Thu Sep  7 17:53:49 2017
all done at: Thu Sep  7 17:53:49 2017

如果将join代码注释掉的话,主线程将不会等待子线程运行,打印结果如下:

starting at: Thu Sep  7 17:56:11 2017
start loop  0  sec: 2  at: Thu Sep  7 17:56:11 2017
start loop  1  sec: 4  at: Thu Sep  7 17:56:11 2017
start loop  2  sec: 5  at: Thu Sep  7 17:56:11 2017
all done at: Thu Sep  7 17:56:11 2017
end loop  0  done at: Thu Sep  7 17:56:13 2017
end loop  1  done at: Thu Sep  7 17:56:15 2017
end loop  2  done at: Thu Sep  7 17:56:16 2017

可以看到all done语句已经先执行完毕。然后各个子线程仍然在运行直到结束。

  1. 创建Thread实例,传给它一个可调用类的实例
    这里需要解释一下,一般实现了__call__方法的类,其实例可以像函数一样进行调用(其实函数就是可调用的对象),称之为可调用类的实例。这样的话,只需要在类中实现__call__方法即可
import random
import threading
from time import ctime, sleep

class ThreadFunc(object):
    def __init__(self,func,args,name=''):
        self.name = name
        self.func = func
        self.args = args

    def __call__(self):
        self.func(*self.args)

def loop(nloop,nsec):
    print('start loop ',nloop,' sec:',nsec,' at:',ctime())
    sleep(nsec)
    print('end loop ',nloop,' at:',ctime())

def main():
    print('start at:',ctime())
    threads = []
    loops = range(3)
    for i in loops:
        t = threading.Thread(target=ThreadFunc(loop,(i,random.randint(1,5)),loop.__name__))
        threads.append(t)

    for i in loops:
        threads[i].start()

    for i in loops:
        threads[i].join()

    print('all done at:',ctime())

if __name__ == '__main__':
    main()

这个例子和上面的例子相同,只不过在实例化Thread的时候将ThreadFunc传递给target,当t调用start的时候,其会调用__call__方法。
看一下运行的结果:

import random
import threading
from time import ctime, sleep


class ThreadFunc(object):
    def __init__(self,func,args,name=''):
        self.name = name
        self.func = func
        self.args = args

    def __call__(self):
        self.func(*self.args)

def loop(nloop,nsec):
    print('start loop ',nloop,' sec:',nsec,' at:',ctime())
    sleep(nsec)
    print('end loop ',nloop,' at:',ctime())

def main():
    print('start at:',ctime())
    threads = []
    loops = range(3)
    for i in loops:
        t = threading.Thread(target=ThreadFunc(loop,(i,random.randint(1,5)),loop.__name__))
        threads.append(t)

    for i in loops:
        threads[i].start()

    for i in loops:
        threads[i].join()

    print('all done at:',ctime())

if __name__ == '__main__':
    main()
  1. 派生Thread的子类,创建子类的实例。
    派生Thread的子类,一般需要重写run方法。
import random
from threading import Thread
from atexit import register
from time import ctime, sleep

class ThreadFunc(Thread):
    def __init__(self,func,args):
        Thread.__init__(self)
        self.func = func
        self.args = args

    def run(self):
        self.func(*self.args)

def loop(nloop,nsec):
    print('start loop ',nloop,' sec:',nsec,' at:',ctime())
    sleep(nsec)
    print('end loop ',nloop,' at:',ctime())

def main():
    print('start at:',ctime())
    threads = []
    loops = range(3)
    for i in loops:
        t = ThreadFunc(loop,(i,random.randint(1,5)))
        threads.append(t)

    for i in loops:
        threads[i].start()

@register
def _atexit():
    print('all done at:',ctime())

if __name__ == '__main__':
    main()

需要注意的是,子类的构造函数必须先调用基类的构造函数,在基类的构造函数中对于相应的参数进行了设置。这里并没有用join来控制主线程等待子线程完成,而是使用atexit.register()来注册一个退出函数。来看一下结果:

start at: Thu Sep  7 18:33:22 2017
start loop  0  sec: 4  at: Thu Sep  7 18:33:22 2017
start loop  1  sec: 3  at: Thu Sep  7 18:33:22 2017
start loop  2  sec: 4  at: Thu Sep  7 18:33:22 2017
end loop  1  at: Thu Sep  7 18:33:25 2017
end loop  0  at: Thu Sep  7 18:33:26 2017
end loop  2  at: Thu Sep  7 18:33:26 2017
all done at: Thu Sep  7 18:33:26 2017

以上就是多线程的三种实现方式。

本节将研究如何使用Thread 类来实现多线程。由于之前已经介绍过锁的基本概念,因此这里不会再对锁原语进行介绍。因为Thread()类同样包含某种同步机制,所以锁原语的显示使用不再是必需的了

同步原语

一般在多线程代码中,一般有一些特定的函数或代码块不希望被多个线程同时执行,这就需要使用同步了。同步原语中,锁是最简单、最低级的机制,而信号量通常用于多线程竞争有限资源的情况。

本文由js12345金沙官网登入发布于网络编程,转载请注明出处:多线程编程(五) threading模块

关键词: