如何优雅地杀死正在监听消息队列的线程

时间:2018-08-03 18:44:06

标签: python multithreading message-queue sigterm

在我的Python应用程序中,我有一个使用来自Amazon SQS FIFO队列的消息的函数。

def consume_msgs():
    sqs = boto3.client('sqs',
                   region_name='us-east-1',
                   aws_access_key_id=AWS_ACCESS_KEY_ID,
                   aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
    print('STARTING WORKER listening on {}'.format(QUEUE_URL))
    while 1:
        response = sqs.receive_message(
            QueueUrl=QUEUE_URL,
            MaxNumberOfMessages=1,
            WaitTimeSeconds=10,
        )
        messages = response.get('Messages', [])
        for message in messages:
            try:
                print('{} > {}'.format(threading.currentThread().getName(), message.get('Body')))
                body = json.loads(message.get('Body'))
                sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=message.get('ReceiptHandle'))

            except Exception as e:
                print('Exception in worker > ', e)
                sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=message.get('ReceiptHandle'))

    time.sleep(10)

为了扩大规模,我正在使用多线程处理消息。

if __name__ == '__main__:

    for i in range(3):
        t = threading.Thread(target=consume_msgs, name='worker-%s' % i)
        t.setDaemon(True)
        t.start()
    while True:
        print('Waiting')
        time.sleep(5)

该应用程序作为服务运行。如果我需要部署新版本,则必须重新启动。当主进程终止时,有没有办法使线程正常存在?而不是突然终止线程,它们首先以当前消息结束,并停止接收下一条消息。

1 个答案:

答案 0 :(得分:2)

由于线程一直在循环,因此不能仅join进行循环,但是您需要向它们发出信号,也该退出循环了,以便能够做到这一点。这个docs提示可能有用:

  

守护程序线程在关闭时突然停止。它们的资源(例如打开的文件,数据库事务等)可能无法正确释放。如果您希望线程正常停止,请将它们设置为非守护进程,并使用适当的信令机制,例如Event

因此,我将以下示例放在一起,希望可以有所帮助:

from threading import Thread, Event
from time import sleep

def fce(ident, wrap_up_event):
    cnt = 0
    while True:
        print(f"{ident}: {cnt}", wrap_up_event.is_set())
        sleep(3)
        cnt += 1
        if wrap_up_event.is_set():
            break
    print(f"{ident}: Wrapped up")

if __name__ == '__main__':
    wanna_exit = Event()
    for i in range(3):
        t = Thread(target=fce, args=(i, wanna_exit))
        t.start()
    sleep(5)
    wanna_exit.set()

将单个事件实例传递到fce,它将连续运行,但是在每次迭代完成后,如果事件已设置为True,则返回最高检查之前。在退出脚本之前,我们从控制线程将此事件设置为True。由于这些线程不再标记为守护程序线程,因此我们不必显式join

根据要关闭脚本的精确程度,您将需要处理SIGTERM的传入信号(也许是KeyboardInterrupt)或SIGINT异常。并在退出之前进行清理,其机制保持不变。除了不让python立即停止执行外,您还需要让线程知道它们不应重新进入循环并等待它们加入。


SIGINT有点简单,因为它作为python异常公开,您可以例如对“ main”位执行以下操作:

if __name__ == '__main__':
    wanna_exit = Event()
    for i in range(3):
        t = Thread(target=fce, args=(i, wanna_exit))
        t.start()
    try:
        while True:
            sleep(5)
            print('Waiting')
    except KeyboardInterrupt:
        pass
    wanna_exit.set()

当然,您不仅可以从控制终端发送SIGINTkill的进程中。