python多线程程序设计 之一
- 全局解释器锁
- 线程APIs
- threading.active_count()
- threading.current_thread()
- threading.excepthook(args, /)
- threading.get_native_id()
- threading.main_thread()
- threading.stack_size([size])
- 线程对象
- 成员函数
- 构造器
- start/run
- join
- 线程子类
- 实列代码
由于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 类表示在单独的控制线程中运行的活动。有两种方法可以指定活动
- 把可调用对象传递给线程构造函数
- 重写子类中的 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
- start, 启动线程的活动。每个线程对象,最多只能调用start一次。它在单独的控制线程中,调用对象的 run() 方法。如果在同一个线程对象上,多次调用start,将产生RuntimeError。
- run, 表示线程活动的方法。run() 方法调用一个可调用的对象,这个可调用对象是由构造器的target参数传递给子类的。构造器的参数args和kwargs就是可调用对象的参数。
join
join引起调用线程等待,直到调用 join() 方法的线程终止。也可能未处理的异常终止线程,或可选的超时引起线程终止。
当timeout参数存在,且非 None 时,它应该是一个浮点数,指定以秒(或其分数)为单位的操作超时。由于 join() 总是返回 None,因此必须在 join() 之后,调用 is_alive() 来判断是否发生超时。如果线程仍然存活,则 join() 调用超时。
当timeout参数不存在,或为 None 时,操作将阻塞,直到线程终止。
一个线程可以被连接多次。
如果尝试join() 当前线程,产生RuntimeError异常,因为这会导致死锁。在线程启动之前,join() 线程也是一个错误,它会引发相同的异常。
线程子类
把threading.Thread作为基类的推导类,产生的类都是线程子类。
子类有两种方法指定活动
- 把可调用对象传递给线程构造函数
- 重写子类中的 run() 方法,子类中不应重写其他方法。换句话说,只重写该类的 init() 和 run() 方法。
实列代码
该实列使用两种不同的方法实现两个线程子类的活动。
- Producer是一个可调用对象,使用线程构造函数,产生线程,该线程的run函数调用该可调用对象。
- Consumer是一个线程子类,该子类包含他自己的run方法,这个方法就是该子类的活动。
这两个子线程使用不同的方法,传递运行参数。
import threading
import randomdef 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_dictprint("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();