如何理解python中的线程

python中由threading包的Thread类来提供线程服务,

Thread类表示在单独的控制线程中运行的代码。有两种方法可以指定其执行的代码:通过将可调用对象传递给构造函数, 或者在子类中重写run()方法。在子类中不应该重写任何其他方法(除了构造函数)。换句话说,只覆盖该类的__init__()和run()方法。

一旦创建一个线程对象,其活动必须通过调用线程启动start()方法。这将在一个单独的控制线程中调用run()方法。

一旦线程的函数启动,该线程就被认为是“活动的”。当它的run()方法终止时,它将停止活动——要么是正常的, 要么是引发一个未处理的异常。is_alive()方法测试线程是否为活动的。

其他线程可以调用线程的join()方法。这将阻塞调用线程,直到调用join()方法的线程终止。 线程有一个名称。可以将名称传递给构造函数,并通过name属性进行读取或更改。 可以将线程标记为“守护进程线程”。这个标志的意义在于,当只剩下守护进程线程时,整个Python程序将退出。 初始值从创建线程继承。可以通过守护进程属性或守护进程构造函数参数设置标志。

请注意 守护进程线程在关闭时突然停止。它们的资源(如打开的文件、数据库事务等)可能不能正确地释放。 如果您希望您的线程优雅地停止,请使它们不是守护进程,并使用合适的信号机制(如事件)。

有一个“主线程”“main thread”对象;这对应于Python程序中的初始控制线程。它不是一个守护进程线程。

start()

启动线程的函数。

它必须在每个线程对象上最多调用一次。 它安排在一个单独的控制线程中调用对象的run()方法。 如果在同一线程对象上调用多次,此方法将引发运行时错误。

run()

表示线程活动的方法。您可以在子类中重写此方法。标准run()方法调用传递给对象构造函数的可调用对象 作为目标参数(如果有的话),并分别使用来自args和kwargs参数的顺序参数和关键字参数。

join(timeout=None)

等待线程终止。这将阻塞调用线程,直到其join()方法被调用的线程终止(通常或通过未处理的异常终止), 或者直到可选超时发生。

当存在超时参数时,它应该是一个浮点数,在秒内指定操作的超时(或其分数)。 因为join()总是返回None,所以必须在join()之后调用is_alive()来决定是否发生超时——如果线程仍然活着,则join()调用超时。

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

线程可以多次调用join()

如果尝试加入当前线程,那么join()将引发运行时错误,因为这会导致死锁。 在线程启动之前join()也是一个错误,尝试这样做会引发相同的异常。

is_alive()

返回线程是否为活动的。

这个方法在run()方法开始之前返回True,直到run()方法结束之后。模块函数enumerate()返回所有活动线程的列表。

在CPython中,由于全局解释器锁,只有一个线程可以同时执行Python代码(即使某些面向性能的库可能克服这个限制)。 如果希望应用程序更好地利用多核机器的计算资源,建议使用多进程(multiprocessing)或concurrent.futures.ProcessPoolExecutor。 但是,如果您想同时运行多个I/o绑定任务,线程仍然是一个合适的模型。

例如多线程FTP上传任务。我们可以创建多个线程,每个线程启动一个上传任务。线程中的任务通过队列(queue)来传递。实际测试发现线程中优化 的性能不大。

queue多生产者多消费者队列

队列模块实现多生产者、多消费者队列。当消息必须在多个线程之间安全地交换时,它在线程编程中尤其有用。 这个模块中的Queue类实现所有必需的锁定语义。这依赖于Python中线程支持的可用性。

该模块实现了三种类型的队列,它们只根据检索条目的顺序不同。在FIFO队列中,添加的第一个消息是第一个检索到的。 在LIFO队列中,最近添加的消息是第一个检索(像堆栈一样操作)。对于优先级队列,将保持对消息的排序(使用heapq模块), 并首先检索值最低的消息。在内部,模块使用锁来临时阻塞相互竞争的线程; 但是,它并不是设计来处理线程中的重入。

class queue.Queue(maxsize=0)

FIFO队列的构造函数。maxsize是一个整数,它设置可以放置在队列中的消息数量的上限。一旦达到这个大小,插入将被阻塞, 直到队列中消息被消费。如果maxsize小于或等于零,则队列大小为无穷大。

class queue.LifoQueue(maxsize=0) 后进先出队列

class queue.PriorityQueue(maxsize=0) 优先级队列