python多线程程序设计 之一


由于Python编程语言的规范实现的全局解释器锁,一次只有一个线程可以执行Python代码。

如果您希望应用程序更好地利用多核机器的计算资源,使用multiprocessing或concurrent.futures.ProcessPoolExecutor。然而,如果您想同时运行多个 I/O 密集型任务,线程仍然是一个合适的模型

全局解释器锁

CPython 解释器使用的一种机制,确保一次只有一个线程执行 Python 字节码。这使对象模型在并发访问的情况下,是安全,简化了 CPython 实现。锁定整个解释器,使解释器更容易成为多线程,但代价是,损失了多处理器机器提供的大部分并行性。

线程APIs

threading.active_count()

返回当前活动的 Thread 对象的数量。返回的计数等于enumerate() 返回的列表的长度。

threading.current_thread()

返回当前 Thread 对象,对应于调用者的控制线程。如果调用者的控制线程不是通过线程模块创建的,则返回一个功能有限的虚拟线程对象。

threading.excepthook(args, /)

处理 Thread.run() 引发的未捕获异常。
args 参数具有以下属性:

  • exc_type:异常类型。
  • exc_value:异常值,可以为None。
  • exc_traceback:异常回溯,可以为None。
  • thread:引发异常的线程,可以为 None。

如果 exc_type 是 SystemExit,则异常将被静默忽略。否则,异常将显示在sys.stderr 上。
如果此函数引发异常,则会调用 sys.excepthook() 来处理它。

可以重写 threading.excepthook() 以控制如何处理 Thread.run() 引发的未捕获异常。

threading.get_native_id()

返回内核分配的当前线程的本机整数线程 ID。这是一个非负整数。它的值可用于在系统范围内唯一标识该特定线程(直到线程终止,之后该值可由操作系统回收)。

threading.main_thread()

返回主线程对象。正常情况下,主线程是Python解释器启动的线程。

threading.stack_size([size])

返回创建新线程时,使用的线程堆栈大小。可选的参数size指定用于后续被创建的线程的堆栈大小。并且,其值必须为 0(使用平台或配置的默认值)或至少为 32,768 (32 KiB) 的正整数值。如果未指定size,则使用值0。如果不支持更改线程堆栈大小,则会引发运行时错误。如果指定的堆栈大小无效,则会引发 ValueError,并且堆栈大小不变。 32 KiB 是当前支持的最小堆栈大小值,以保证解释器本身有足够的堆栈空间。

某些平台可能对堆栈大小的值有特殊限制,例如要求最小堆栈大小 > 32 KiB,或要求以系统内存页面大小的倍数进行分配。

线程对象

Thread 类表示在单独的控制线程中运行的活动。有两种方法可以指定活动

  1. 把可调用对象传递给线程构造函数
  2. 重写子类中的 run() 方法,子类中不应重写其他方法。换句话说,只重写该类的 init() 和 run() 方法。

一旦创建了线程对象,就必须通过调用线程的 start() 方法来启动其活动。这会在单独的控制线程中调用 run() 方法。
一旦线程的活动开始,该线程就被认为是“活动的”。当它的 run() 方法正常终止,或通过引发未处理的异常终止时,它就停止活动。 is_alive() 方法测试线程是否存活。

其他线程可以调用一个线程的 join() 方法。这会阻塞调用线程,直到调用 join() 方法的线程终止。

线程有一个名称。名称可以传递给构造函数,并通过 name 属性读取或更改。如果 run() 方法引发异常,则会调用 threading.excepthook() 来处理它。默认情况下,threading.excepthook() 会默默地忽略 SystemExit。

线程可以被标记为“守护线程”。该标志的意义在于,当只剩下守护线程时,整个Python程序就会退出。初始值是从创建线程继承的。该标志可以通过 daemon 属性,或daemon构造函数参数来设置。有一个“主线程”对象;它是Python程序中的初始控制线程。它不是守护线程。有可能创建“虚拟线程对象”。这些是与“外来线程”相对应的线程对象,这些线程是在线程模块外部启动的控制线程,例如直接从 C 代码启动。

成员函数

构造器

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

  • group,应该是None;保留,用于将来扩展ThreadGroup 类。
  • target,是run() 方法可调用的对象。默认为 None,表示不调用任何内容。
  • name,是线程名称。默认情况下,唯一名称的构造形式为“Thread-N”,其中 N 是一个小十进制数,或者为“Thread-N (target)”,其中“target”是target.name(如果指定了target参数)。
  • args,是目标调用的参数列表或元组。默认为()。
  • kwargs,是目标调用的关键字参数的字典。默认为 {}。
  • daemon,如果不是 None,则设置线程是为守护线程。如果 None (默认值),则守护进程属性将从当前线程继承。

如果子类重写构造函数,则必须确保,在对线程执行任何其他操作之前,调用基类构造函数 (Thread.init())。

start/run

  1. start, 启动线程的活动。每个线程对象,最多只能调用start一次。它在单独的控制线程中,调用对象的 run() 方法。如果在同一个线程对象上,多次调用start,将产生RuntimeError。
  2. run, 表示线程活动的方法。run() 方法调用一个可调用的对象,这个可调用对象是由构造器的target参数传递给子类的。构造器的参数args和kwargs就是可调用对象的参数。

join

join引起调用线程等待,直到调用 join() 方法的线程终止。也可能未处理的异常终止线程,或可选的超时引起线程终止。

当timeout参数存在,且非 None 时,它​​应该是一个浮点数,指定以秒(或其分数)为单位的操作超时。由于 join() 总是返回 None,因此必须在 join() 之后,调用 is_alive() 来判断是否发生超时。如果线程仍然存活,则 join() 调用超时。

当timeout参数不存在,或为 None 时,操作将阻塞,直到线程终止。

一个线程可以被连接多次。

如果尝试join() 当前线程,产生RuntimeError异常,因为这会导致死锁。在线程启动之前,join() 线程也是一个错误,它会引发相同的异常。

线程子类

把threading.Thread作为基类的推导类,产生的类都是线程子类。

子类有两种方法指定活动

  1. 把可调用对象传递给线程构造函数
  2. 重写子类中的 run() 方法,子类中不应重写其他方法。换句话说,只重写该类的 init() 和 run() 方法。

实列代码

该实列使用两种不同的方法实现两个线程子类的活动。

  • Producer是一个可调用对象,使用线程构造函数,产生线程,该线程的run函数调用该可调用对象。
  • Consumer是一个线程子类,该子类包含他自己的run方法,这个方法就是该子类的活动。

这两个子线程使用不同的方法,传递运行参数。

import threading
import random

def Producer(v1, v2, v3, name, age):
    print("Producer args: {0} name:{1} age : {2}".format([v1,v2,v3], name, age))
    
class Consumer(threading.Thread):
    def __init__(self, key_dict):
        super(Consumer,self).__init__()
        self.keys = key_dict
        print("Consumer")

    def run(self):
        print("dict: {0}".format(self.keys))

if __name__ == "__main__":
    v_list = [1,2,3]
    keys = {"name":"John", "age":"50"}
    producer = threading.Thread(target=Producer, args=v_list, kwargs=keys)
    consumer = Consumer(keys)
    producer.start();
    consumer.start();
    
    producer.join()
    consumer.join();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值