34

Python 多线程小技巧:比 time.sleep 更好用的暂停写法!

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzUyOTk2MTcwNg%3D%3D&%3Bmid=2247487570&%3Bidx=2&%3Bsn=628b0695555eef851254510edd1f4929
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

 △点击上方 Python猫 ”关注 ,回复“ 1 ”领取电子书

f6beEfe.jpg!mobile

剧照:凡人修仙传

作者:kingname

来源:未闻Code

我们知道,在 Python 里面可以使用 time.sleep 来让代码暂停一段时间,例如:

import time

print('...部分代码...')
time.sleep(5)
print('...剩下的代码...')

程序首先打印出 ...部分代码... ,然后等待5秒钟,再打印出 ...剩下的代码...

现在大家想一想,有没有什么办法,在不使用 time.sleep 的情况下,让程序暂停5秒?

你可能会说,用 requests 访问一个延迟5秒的网址、或者用递归版算法计算斐波那契数列第36位……这些奇技淫巧。

不过今天我说的,是另外一个东西, threading 模块里面的 Event

我们来看看它的用法:

import threading

event = threading.Event()
print('...部分代码...')
event.wait(5)
print('...剩下的代码...')

这样一来,程序首先打印出 ...部分代码... ,然后等待5秒钟,再打印出 ...剩下的代码...

功能看起来跟 time.sleep 没什么区别,那为什么我要特别提到它呢?因为在多线程里面,它比 time.sleep 更有用。我们来看一个例子:

import threading

class Checker(threading.Thread):
    def __init__(self, event):
        super().__init__()
        self.event = event

    def run(self):
        while not self.event.is_set():
            print('检查 redis 是否有数据')
            time.sleep(60)

trigger_async_task()
event = threading.Event()
checker = Checker(event)
checker.start()
if user_cancel_task():
    event.set()

我来解释一下这段代码的意思。在主线程里面,我调用 trigger_async_task() 触发了一个异步任务。这个任务多久完成我并不清楚。但是这个任务完成以后,它会往 Redis 里面发送一条消息,只要 Redis 有这个消息了,我就知道它完成了。所以我要创建一个 checker 子线程,每60秒去 Redis里面检查任务是否完成。如果没有完成,就暂停60秒,然后再检查。

但某些情况下,我不需要等待了,例如用户主动取消了任务。这个时候,我就想提前结束这个 checker 子线程。

但是我们知道,线程是不能从外面主动杀死的,只能让它自己退出。所以当我执行 event.set() 后,子线程里面 self.event.is_set() 就会返回 False,于是这个循环就不会继续执行了。

可是,如果某一轮循环刚刚开始,我在主线程里面调用了 event.set() 。此时,子线程还在 time.sleep 中,那么子线程需要等待60秒才会退出。

但如果我修改一下代码,使用 self.event.wait(60)

import threading

class Checker(threading.Thread):
    def __init__(self, event):
        super().__init__()
        self.event = event

    def run(self):
        while not self.event.is_set():
            print('检查 redis 是否有数据')
            self.event.wait(60)

trigger_task()
event = threading.Event()
checker = Checker(event)
checker.start()
if user_cancel_task():
    event.set()

那么,即便 self.event.wait(60) 刚刚开始阻塞,只要我在主线程中执行了 event.set() ,子线程里面的阻塞立刻就会结束。于是子线程立刻就会结束。不需要再白白等待60秒。

并且, event.wait() 这个函数在底层是使用 C 语言实现的,不受 GIL 锁的干扰。

猫注:原文标题《一日一技:除了time.sleep,你还有一个暂停代码的方法》

Python猫技术交流群开放啦! 群里既有国内一二线大厂在职员工,也有国内外高校在读学生,既有十多年码龄的编程老鸟,也有中小学刚刚入门的新人,学习氛围良好!想入群的同学,请在公号内回复『 交流群 』,获取猫哥的微信 (谢绝广告党,非诚勿扰!) ~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK