Using Threads and Signal makes unhang program

本文介绍如何使用多线程和信号处理技术防止GUI和控制台应用程序在长时间运行任务时挂起。通过示例代码展示了如何在Python中利用多线程进行后台任务处理,同时保持界面响应,并使用信号处理优雅地结束程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Using Threads and Signal makes unhang program

keywords: Threads; signal; hang; Python; Ctrl+C; SIGINT

一篇面向没有多线程,捕获信号方面编程经验 的 使用多线程以及捕获信号实现的 console/GUI 不hang 的简单 demo。

写这一篇 blog 的起因是一位同事问 GUI 上一个按钮点击之后,耗时太长, GUI hang 住。如何使用 多线程 使 GUI 不 hang。
实际上使用多线程使 GUI 不 hang 住还是相当容易实现的。
但是我个人非常能够感受到这种心理,就是大概知道怎么做,但是没有做过,就有一种去学多线程,捕获信号处理等等这种的障碍。
所以,我写了这篇 blog,重在示例,多线程和捕捉信号这类编程技术不算很难掌握,实际上只需要不到十行代码,已经适当调整一下代码结构(封装一些代码到一个函数中去)就能够使用了。


不过为了写这些一步一步的示例和录制 GIF 着实花了我很多时间。先发表上来,等什么时候有需要补充要说的细节了再来 update。

Demo

SIGINT_Ctrl+C.py

Usage
$ chmod +x ./SIGINT_Ctrl+C.py
$ 
$ ./SIGINT_Ctrl+C.py
......
Ctrl+C
.....
$ 
Code
#!/usr/bin/env python3

"""https://github.com/RDpWTeHM/solution/tree/master/unhang/threads_and_signal/
SIGINT_Ctrl+C.py
Author: Joseph Lin
Email : joseph.lin@aliyun.com
Social:
  https://github.com/RDpWTeHM
  https://blog.youkuaiyun.com/qq_29757283
Note:
  Ctrl+C <==> SIGINT
"""

import signal
from time import sleep

# Signal handler for termination (required)
def sigint_handler(signo, frame):
    ''' show catch Ctrl+C information!'''
    print("\nGoodbay Cruel World.....\n")
    raise SystemExit(1)

def prog_init():
    signal.signal(signal.SIGINT, sigint_handler)

def main():
    prog_init()

    counter = 0
    while True:
        print("Guess what am I running....{}s".format(counter), end="")
        sleep(1)
        counter += 1
        print("\r", end="")


if __name__ == '__main__':
    main()
Test Result

simple_catch_Ctrl+C=SIGINT_demo

SIGALRM_calculate-game.py

Usage
$ chmod +x ./SIGALRM_calculate-game.py
$ 
$ ./SIGALRM_calculate-game.py 
[Debug] prog_pid <- os.getpid(): 28202
!!!!!game start!!!!!
Calculate 10 x 4 = ?: 40
Right!

Calculate 8 x 6 = ?: 48
Right!

Calculate 6 x 5 = ?: 
Oooooops game time up!

Final score: 2

$ 
Code
#!/usr/bin/env python3

"""https://github.com/RDpWTeHM/solution/tree/master/unhang/threads_and_signal/
SIGALRM_calculate-game.py
Author: Joseph Lin
Email : joseph.lin@aliyun.com
Social:
  https://github.com/RDpWTeHM
  https://blog.youkuaiyun.com/qq_29757283
Note:
  signal.alarm(integer) ==> SIGALRM
"""

import sys
import os
import signal
from random import randint

score = 0
prog_pid = os.getpid()
if __debug__:
    print("[Debug] prog_pid <- os.getpid(): {}".format(prog_pid))

def sigalrm_handler(signo, frame):
    ''' game time up'''
    global prog_pid
    print("\nOooooops game time up!")
    os.kill(prog_pid, signal.SIGINT)

def sigint_handler(signo, frame):
    ''' end game'''
    global score
    print("\nFinal score: {}\n".format(score))
    raise SystemExit(0)

def prog_init():
    signal.signal(signal.SIGALRM, sigalrm_handler)
    signal.signal(signal.SIGINT, sigint_handler)

def main():
    global score
    global prog_pid

    prog_init()

    print("!!!!!game start!!!!!")

    while True:
        a = randint(0, 10)
        b = randint(0, 10)

        signal.alarm(5)  # set game time
        print("Calculate {} x {} = ?: ".format(a, b), end='')
        try:
            user_input = int(input())
        except ValueError:
            print("Error input! Expect numbers.", file=sys.stderr)
            os.kill(prog_pid, signal.SIGINT)

        if a * b == user_input:
            print("Right!\n")
            score += 1
        else:
            os.kill(prog_pid, signal.SIGINT)

if __name__ == '__main__':
    main()
Test Result

set-Alarm_SIGALRM

unhang_console_by_Threads_SIGALRM.py

Usage
$ pyton -c "import requests"
## run "pip3 install --user requests" if needed
$ 
$ time ./unhang_console_by_Threads_SIGALRM.py 
Test Result
  • 一个 console 转圈圈的程序
    Code

    #!/usr/bin/env python3
    import signal
    from time import sleep
    
    def sigint_handler(signo, frame):
        ''' end program'''
        print("")
        raise SystemExit(0)
    
    def prog_init():
        signal.signal(signal.SIGINT, sigint_handler)
    
    def main():
        prog_init()
        i = 0
        circle = ('|', '/', '-', '\\')
        while True:
            sleep(0.2)
            print("\rprocessing... {}".format(circle[i]), end='')
            i = 0 if i == 3 else i + 1
    
    if __name__ == '__main__':
        main()
    

    Running
    circle

  • normal coding = console hang example:
    Code:

    #!/usr/bin/env python3
    
    import os
    import signal
    from time import sleep
    	
    request_pages_result = {}
    prog_pid = os.getpid()
    if __debug__:
        print("[Debug] prog_pid <- os.getpid(): {}".format(prog_pid))
    	
    def sigint_handler(signo, frame):
        ''' end program'''
        print("")
        raise SystemExit(0)
    	
    def prog_init():
        signal.signal(signal.SIGINT, sigint_handler)
    	
    def request_pages():
        global request_pages_result
        sites = ("https://www.baidu.com",
                 "https://www.bing.com",
                 "https://www.yahoo.com",
                 "http://www.so.com")
    
        import requests
        try:
            for site in sites:
                r = requests.get(site)
                if r.status_code != 200:
                    request_pages_result[site] = False
                else:
                    request_pages_result[site] = True
        except Exception as e:
            print("[Error] request_pages(): ", e, file=sys.stderr)
    
    
    def main():
        global request_pages_result
        global prog_pid
    
        prog_init()
    	
        request_pages()
        print(request_pages_result)
    
        i = 0
        circle = ('|', '/', '-', '\\')
        while True:
            sleep(0.2)
            print("\rprocessing... {}".format(circle[i]), end='')
            i = 0 if i == 3 else i + 1
    	
    if __name__ == '__main__':
        main()
    

    Usage:

    $ pyton -c "import requests"
    ## run "pip install --user requests" if needed
    $ 
    $ time ./unhang_console_by_Threads_SIGALRM.py 
    [Debug] prog_pid <- os.getpid(): 28334
    {'https://www.baidu.com': True, 'https://www.bing.com': True, 'https://www.yahoo.com': True, 'http://www.so.com': True}
    processing... \^C
    
    real    0m17.423s
    user    0m0.268s
    sys	    0m0.026s
    $ 
    

    Running:
    console_hang_example

  • use Thread, but main-thread wait sub-thread finish, then show the result, still console hang.

    $ git diff
    [...]
    --- a/unhang/threads_and_signal/unhang_console_by_Threads_SIGALRM.py
    +++ b/unhang/threads_and_signal/unhang_console_by_Threads_SIGALRM.py
    @@ -19,6 +19,7 @@ import os
     import signal
     from time import sleep
    +import threading
     
     
     request_pages_result = {}
    @@ -72,8 +73,11 @@ def main():
        
    -    request_pages()
    -    print(request_pages_result)
    +    t = threading.Thread(target=request_pages)
    +    t.setDaemon(True)
    +    t.start()
    +    t.join()
    +    print("request pages result: \n", request_pages_result)
     
         i = 0
         circle = ('|', '/', '-', '\\')
    $
    

    run $ time ./unhang_console_by_Threads_SIGALRM.py just look like the same.

  • use threads, no .join() , catch “Ctrl+C” before quit program, show the result.

    so, if run time is short, the function will not finish (t.setDaemon(True) must require). we can see different result if “processing” time is short and long.
    Code

    #!/usr/bin/env python3
    
    import signal
    from time import sleep
    import threading
    
    request_pages_result = {}
    
    def sigint_handler(signo, frame):
        ''' end program'''
        global request_pages_result
        print("\nGoodbay Cruel World.....")
        print("before exit program, show you the result:\n", request_pages_result)
        raise SystemExit(0)
    
    def prog_init():
        signal.signal(signal.SIGINT, sigint_handler)
    
    def request_pages():
        global request_pages_result
        sites = ("https://www.baidu.com",
                 "https://www.bing.com",
                 "https://www.yahoo.com",
                 "http://www.so.com")
    
        import requests
        try:
            for site in sites:
                r = requests.get(site)
                if r.status_code != 200:
                    request_pages_result[site] = False
                else:
                    request_pages_result[site] = True
        except Exception as e:
            print("[Error] request_pages(): ", e, file=sys.stderr)
    
    def main():
        global request_pages_result
    
        prog_init()
    
        t = threading.Thread(target=request_pages)
        t.setDaemon(True)
        t.start()
        print("request pages result: \n", request_pages_result)
    
        i = 0
        circle = ('|', '/', '-', '\\')
        while True:
            sleep(0.2)
            print("\rprocessing... {}".format(circle[i]), end='')
            i = 0 if i == 3 else i + 1
    
    if __name__ == '__main__':
        main()
    

    Running:threads_half_unhang

  • use threads with signal, unhang console.
    Code:

    #!/usr/bin/env python3
    
    """unhang_console_by_Threads_SIGALRM.py
    Author: Joseph Lin
    Email : joseph.lin@aliyun.com
    
    Social:
      https://github.com/RDpWTeHM
      https://blog.youkuaiyun.com/qq_29757283
    
    Note:
      signal.alarm(integer) ==> SIGALRM
      alarm may can't not work well with sleep in some python version.
    """
    
    import os
    import signal
    from time import sleep
    import threading
    	
    request_pages_result = {}
    prog_pid = os.getpid()
    if __debug__:
        print("[Debug] prog_pid <- os.getpid(): {}".format(prog_pid))
    	
    def sigalrm_handler(signo, frame):
        ''' time up'''
        global request_pages_result
        global prog_pid
        print("\nOooooops time up!")
        print("[sigalrm_handler] request pages result: \n", request_pages_result)
        os.kill(prog_pid, signal.SIGINT)
    	
    def sigint_handler(signo, frame):
        ''' end program'''
        global request_pages_result
        print("\nGoodbay Cruel World.....")
        print("[sigint_handler] before exit program, show you the result:\n", request_pages_result)
        raise SystemExit(0)
    	
    def prog_init():
        signal.signal(signal.SIGALRM, sigalrm_handler)
        signal.signal(signal.SIGINT, sigint_handler)
    	
    def request_pages():
        global request_pages_result
        sites = ("https://www.baidu.com",
                 "https://www.bing.com",
                 "https://www.yahoo.com",
                 "http://www.so.com")
    
        import requests
        try:
            for site in sites:
                r = requests.get(site)
                if r.status_code != 200:
                    request_pages_result[site] = False
                else:
                    request_pages_result[site] = True
        except Exception as e:
            print("[Error] request_pages(): ", e, file=sys.stderr)
    	
    def main():
        global request_pages_result
        global prog_pid
    
        prog_init()
    
        t = threading.Thread(target=request_pages)
        t.setDaemon(True)
        t.start()
        print("request pages result: \n", request_pages_result)
        print("start the function which will cost lots of time.")
    
        # 5 second later, run the register function which will show the result.
        signal.alarm(5)
    
        # main thread keep doing things
        i = 0
        circle = ('|', '/', '-', '\\')
        while True:
            sleep(0.2)
            print("\rmain thread processing... {}".format(circle[i]), end='')
            i = 0 if i == 3 else i + 1
    	
    if __name__ == '__main__':
        main()
    

    Running:threads_and_signal_unhang

unhang_GUI_by_Threads.py

Usage
$ python -c "import tkinter"
## "pip3 install --user tkinter" if needed
## "requests" is also needed in this example
$ 
$ python ./unhang_GUI_by_Threads.py

Test Result
  • basic GUI demo

    click button, show message on GUI.
    Code:

    #!/usr/bin/env python3
    from tkinter import *
    import tkinter as tk
    import sys
    
    def quit():
        print("I will quit!")
        sys.exit()
    
    def clean():
        print("Press Clean button")
        entry.delete(0, tk.END)
    
    def show():
        print("Press Show button")
        entry.delete(0, tk.END)
        entry.insert(0, "Button Clicked!")
    
    def show_request_pages_reslut():
        global request_pages_result
        entry.delete(0, tk.END)
        entry.insert(0, "{!s}".format(request_pages_result))
    
    root = Tk()
    
    win = Frame(root)
    win.pack(expand=YES, fill=BOTH)
    
    entry = Entry(win)
    entry.config(text = "not clicked")
    entry.pack(expand=YES, fill=BOTH)
    
    btnShow = Button(win)
    btnShow.config(text="Show", command=show)
    btnShow.pack(expand=YES, fill=BOTH)
    
    btnClean = Button(win)
    btnClean.config(text="Clean", command=clean)
    btnClean.pack(expand=YES, fill=BOTH)
    
    btnQuit = Button(win)
    btnQuit.config(text='Quit', command=quit)
    btnQuit.pack()
    
    root.title('unhang GUI.py')
    
    root.mainloop()
    

    Running:python-GUI-button_and_showTextonEntry

    GitHub SHA: 4a08bf4dd92dcc5ca0f2be1a3e72cf5bb5c34948

  • GUI hang demo

    Note: this run on Windows system, the phenomenon of hang can’t be able to reproduce on Ubuntu.
    Code:

    #!/usr/bin/env python3
    from tkinter import *
    import tkinter as tk
    import sys
    
    request_pages_result = {}
    
    def quit():
        print("I will quit!")
        sys.exit()
    
    def clean():
        print("Press Clean button")
        entry.delete(0, tk.END)
    
    def show():
        print("Press Show button")
        entry.delete(0, tk.END)
        entry.insert(0, "Button Clicked!")
    
        global request_pages_result
        sites = ("https://www.baidu.com",
                 "https://www.bing.com",
                 "https://www.yahoo.com",
                 "http://www.so.com")
    
        import requests
        try:
            for site in sites:
                r = requests.get(site)
                if r.status_code != 200:
                    request_pages_result[site] = False
                else:
                    request_pages_result[site] = True
        except Exception as e:
            print("[Error] request_pages(): ", e, file=sys.stderr)
    
        show_request_pages_reslut()
    
    def show_request_pages_reslut():
        global request_pages_result
        entry.delete(0, tk.END)
        entry.insert(0, "{!s}".format(request_pages_result))
    
    root = Tk()
    
    win = Frame(root)
    win.pack(expand=YES, fill=BOTH)
    
    entry = Entry(win)
    entry.config(text = "not clicked")
    entry.pack(expand=YES, fill=BOTH)
    
    btnShow = Button(win)
    btnShow.config(text="Show", command=show)
    btnShow.pack(expand=YES, fill=BOTH)
    
    btnClean = Button(win)
    btnClean.config(text="Clean", command=clean)
    btnClean.pack(expand=YES, fill=BOTH)
    
    btnQuit = Button(win)
    btnQuit.config(text='Quit', command=quit)
    btnQuit.pack()
    
    root.title('unhang GUI.py')
    
    root.mainloop()
    

    Running:python-GUI_press-button_hang_example

    Button running a time-consuming processing, GUI will hang on Windows system.
    GitHub SHA: d07b97cd1226f23eb9e644288cb8eeae1a685d3e

  • GUI unhang by threads demo
    no need signal in this case, threads make GUI unhang
    Code:

    #!/usr/bin/env python3
    from tkinter import *
    import tkinter as tk
    import sys
    import threading
    	
    request_pages_result = {}
    	
    def quit():
        print("I will quit!")
        sys.exit()
    	
    def clean():
        print("Press Clean button")
        entry.delete(0, tk.END)
    	
    def show():
        print("Press Show button")
        entry.delete(0, tk.END)
        entry.insert(0, "Processing request pages...")
        t = threading.Thread(target=request_pages)
        t.setDaemon(True)
        t.start()
    
    def request_pages():
        global request_pages_result
        sites = ("https://www.baidu.com",
                 "https://www.bing.com",
                 "https://www.yahoo.com",
                 "http://www.so.com")
    
        import requests
        try:
            for site in sites:
                r = requests.get(site)
                if r.status_code != 200:
                    request_pages_result[site] = False
                else:
                    request_pages_result[site] = True
        except Exception as e:
            print("[Error] request_pages(): ", e, file=sys.stderr)
        show_request_pages_reslut()
    	
    def show_request_pages_reslut():
        global request_pages_result
        entry.delete(0, tk.END)
        entry.insert(0, "{!s}".format(request_pages_result))
    
    root = Tk()
    
    win = Frame(root)
    win.pack(expand=YES, fill=BOTH)
    
    entry = Entry(win)
    entry.config(text = "not clicked")
    entry.pack(expand=YES, fill=BOTH)
    
    btnShow = Button(win)
    btnShow.config(text="Show", command=show)
    btnShow.pack(expand=YES, fill=BOTH)
    
    btnClean = Button(win)
    btnClean.config(text="Clean", command=clean)
    btnClean.pack(expand=YES, fill=BOTH)
    
    btnQuit = Button(win)
    btnQuit.config(text='Quit', command=quit)
    btnQuit.pack()
    
    
    root.title('unhang GUI.py')
    
    root.mainloop()
    

    Runing:python-GUI_press-button_unhang_by_threads

    GitHub SHA: c2a9b0215f9ee5cd2d8e458255e5ae9f9447e16d

codes of demo above, using Git log can see the different version code.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值