06.22 Python 模块 线程 Threading

Python 模块 线程 Threading

使用线程(threading),可以在一个进程(process)中,同时进行多个操作。

使用线程对象(Thread Objects)


最简单的使用线程的方法就是,实例化线程,传递一个 target 函数,然后调用 start() 方法开始执行。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

以上生成了3个线程,使用 target 参数指定了线程要运行的函数 worker,3个线程同时运行了函数。

向线程传递参数


可以在生成线程的时候,给线程传递参数,告诉它工作时的一些信息。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

这个例子向线程传递了参数,通过 args 参数,以元组的形式传入到工作线程中。worker 函数按顺序接收到了参数。

给线程命名


生成线程的时候,可以通过参数 name 给线程指定一个名称。在线程运行的时候,可以通过名称判断运行的是哪个线程。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

输出打印了线程的名称。是通过 current_thread() 方法获取到当前线程,再调用线程的 getName() 方法获取到线程名称。如果不指定 name 参数,会提供一个默认名称。

后台线程(Daemon Threads)


上面的例子中,都需要等待线程完成工作,主程序才会退出。有时候,需要把线程放到后台执行,而不干扰主程序的退出。

要生成一个后台线程,需要传递参数 daemon=True 或者调用方法 setDaemon()。默认生成的不是后台线程。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

可见,后台线程 worker() 函数最后一行没有打印,主程序就退出了。

如果要等待一个线程执行完后,才继续执行主程序,需要调用 join() 方法

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

两个线程都完成了自己的工作,全都打印了信息。

默认 join() 方法会一直等待线程结束,你也可以传递一个超时值,如果超时,就算线程没有完成工作,也不会再等待了。

如果我们把上例改成 t.join(0.1),则输出为:

Python 模块 线程 Threading

因为后台线程要休眠1秒,t.join(0.1) 超时值 0.1 秒,显然不会等待它执行完,所以主程序很快退出了。

线程子类


另一种运行线程的方法是,定义一个类,继承自线程基类 Thread。然后定义一个 run() 方法:

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

实例化对象后,直接调用 start() 方法,启动线程。

向线程子类传递参数


Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

类 A 实现了继承的 Thread 相同的接口,可以传递参数如:组 group,目标函数 target,线程名称 name 和 把线程放入后台的 daemon。

这里使用了 args 和 kwargs 传递信息,你也可以自定义传入哪些参数,最后不要忘了调用 父类 super().__init__() 方法。

线程 Timer


线程模块 threading 提供类 Timer,允许隔一段时间后,再开始运行线程。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

线程 t = threading.Timer(1, worker) 1秒后开始运行,另一个 t2 2秒后开始运行。

可以通过 cancel() 方法取消线程运行。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

主程序在 1.2 秒的时候,调用 cancel() 方法取消了线程 t2, 因为 t2 2秒后才开始执行,所以没有输出 t2 的信息。

线程间的信号(Signal)


虽然线程是各自并发的执行,但是有时候在线程间需要同步(synchronize)一些操作。使用 Event 对象是一个简单的方式,它使用内部的标志位,可以通过 set() 和 clear() 设置和取消,其他线程可以使用 wait() 判断,直到标志位设置成功,线程才会继续执行。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

当主程序 Event 调用 set() 方法后,两个线程才开始执行。

wait() 方法,接收一个参数:要等待的秒数,返回的是一个 Boolean 类型,告诉我们事件对象 Event 里的标志位是否设置过了。还可以通过 is_set() 函数判断是否设置过了,它不会阻塞。

worker_timeout() 函数会重复判断是否设置了标志位,直到设置了才会退出,例如我们更改主程序休眠的时间:

Python 模块 线程 Threading

返回的是:

Python 模块 线程 Threading

第一次调用 wait(2) 2秒的时候,还没有设置,所以打印 event is set: False,然后继续循环,第二次设置成功。

控制资源访问


多线程的程序中,很重要的是控制如何访问共享的数据。Python 内置的数据结构(built-in data structures)如:列表(Lists)、字典(Dictionaries)等都是线程安全的(thread-safe),因为有全局解释器锁(Global Interpreter Lock)保护他们不会在一个线程访问的时候另一个线程操作数据。其他的数据结构如:整形、浮点型等都缺乏这样的保护。

为了保护一个对象不会被多个线程同时访问,使用 Lock 对象。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

这个例子中,worker() 函数传入计数器对象,循环3次,计数器调用方法 incr()自增3次。每次调用 incr() 时,调用 time.sleep() 方法,这样允许切换其他线程,让他们交替执行计数器自增方法。

incr() 每次进行自增操作时,都调用 acquire() 获取锁,然后使用 try finally 语句确保发生任何情况下都释放锁。

一共开启了3个线程,每个线程分别执行3次计数器对象的自增方法,最后返回正确的值:9。

最后遍历线程,使用了 threading.enumerate() 方法,排除了主线程,每个子线程调用 join() 方法,等待他们执行完毕。

另外,acquire() 默认会阻塞当前线程。你可以传递参数 False 或者 0,这样调用时,不会阻塞当前线程,它返回当前线程是否获取到了锁,根据返回值,做想要的操作。

Re-entrant Lock

正常的锁不能获取(acquire)多次,即使在同一个线程里:

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

第二次调用 acquire() 传入0是为了不阻塞当前主线程,因为锁已经让第一个语句获取了。

想要重新获取锁,只需要使用 RLock 替代。

Python 模块 线程 Threading

Lock 上下文管理(Context Managers)


锁 Locks 实现了上下文接口,兼容 with 语句。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

使用 with 语句,会自动获取和释放锁,它和使用 try finally 效果是一样的。

对象 Condition 同步线程


除了使用 Event 对象,还可以使用 Condition 对象同步线程,因为它使用了 Lock 锁对象,在多个线程访问同一个资源时会等待资源更新完毕。

下面的例子,实现了生产者-消费者模式(producer-consumer),消费者(consumer)等待状态改变才会执行。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

这个例子,生成了一个生产者线程 producer 和两个消费者线程 consumer,消费者一开始调用 Condition 对象的 wait() 方法等待,知道生产者 producer 开始执行,使用 notifyAll() 方法更改了 Condition 对象的状态,才开始执行。

threading 还提供一个类 Barrier ,它接收一个数量,只有当线程 wait() 到这个数量时,所有的线程才会执行。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

这个例子中,Barrier 对象在等待的数量达到3之前会一直阻塞,然后所有的线程会一起执行。 wait() 函数返回的是释放的数量,你可以根据返回的数量,清理一些资源。

使用 Barrier 的 abort() 方法,会使所有的线程抛出 BrokenBarrierError异常,可以在 wait() 方法上捕获这个异常,清理资源。

限制资源的访问


有时候,允许多个线程同时访问某一资源,只是限制一下总的线程数量。例如,一个连接池支持一定数量的并发连接,或者一个网络应用支持一定的并发下载。Semaphore就是用来处理这种情况的。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

这个例子中,ActivePool 只是用来跟踪某一时刻活动的线程。在创建 Semaphore的时候,限制了最多能同时运行2个线程,从输出可以看到这个结果。

线程自己的数据


除了需要同步访问共享的数据外,有时候线程还需要有自己的数据,这部分数据对外部来说是隐藏的,外部无法访问。可以使用 local() 方法或者自定义类继承 local。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

可以看到,虽然传递都是变量 data ,但是在每个线程中初始值都没有属性 value。

为了初始化属性 value,可以使用类继承自 local ,然后在 __init__() 初始化数据。

Python 模块 线程 Threading

执行:

Python 模块 线程 Threading

在每个线程,__init__ 都会首先执行,初始化 value 为100。


分享到:


相關文章: