SoFunction
Updated on 2025-03-02

Explore the solution to the python multithreaded ctrl+c exit problem

Scene:

You often encounter the following problems: Many io busy applications adopt multi-threading methods to solve them, but at this time you will find that the python command line does not respond to ctrl-c, and the corresponding java code has no problems:

Copy the codeThe code is as follows:

public class Test { 
    public static void main(String[] args) throws Exception { 
 
        new Thread(new Runnable() { 
 
            public void run() { 
                long start = (); 
                while (true) { 
                    try { 
                        (1000); 
                    } catch (Exception e) { 
                    } 
                    (()); 
                    if (() - start > 1000 * 100) break; 
                } 
            } 
        }).start(); 
 
    } 

java Test

ctrl-c will end the program

And the corresponding python code:

Copy the codeThe code is as follows:

# -*- coding: utf-8 -*- 
import time 
import threading 
start=() 
def foreverLoop(): 
    start=() 
    while 1: 
        (1) 
        print () 
        if ()-start>100: 
            break 
              
thread_=(target=foreverLoop) 
#thread_.setDaemon(True) 
thread_.start() 

python

After ctrl-c does not work at all.

Immature Analysis:

First of all, setting daemon to true is definitely not possible, so I won’t explain it. When daemon is false, after importing the python thread library, threading will check whether there are threads that are not daemon after the main thread is executed. Some of them will wait and wait for the thread to end. During the main thread waiting period, all signals sent to the main thread will also be blocked. You can add the signal module to verify it in the above code:

Copy the codeThe code is as follows:

def sigint_handler(signum,frame):   
    print "main-thread exit" 
    ()   
(,sigint_handler) 

Pressing ctrl-c within 100 seconds does not respond. The print "main-thread exit" will appear only after the child thread is finished. It can be seen that ctrl-c is blocked.

Operations performed at the end of the main thread in threading:

Copy the codeThe code is as follows:

_shutdown = _MainThread()._exitfunc 
def _exitfunc(self): 
        self._Thread__stop() 
        t = _pickSomeNonDaemonThread() 
        if t: 
            if __debug__: 
                self._note("%s: waiting for other threads", self) 
        while t: 
            () 
            t = _pickSomeNonDaemonThread() 
        if __debug__: 
            self._note("%s: exiting", self) 
        self._Thread__delete() 
 

Add to join wait for all non-daemon threads, where the source code can be viewed by itself, and wait is called again. As analyzed above, the main thread waits for a lock.

Immature solutions:

You can only set the thread to daemon to prevent the main thread from waiting and accepting the ctrl-c signal, but you cannot allow the child thread to end immediately. Then you can only use the traditional polling method and use sleep to save some CPU intermittently:
 

Copy the codeThe code is as follows:

# -*- coding: utf-8 -*- 
import time,signal,traceback 
import sys 
import threading 
start=() 
def foreverLoop(): 
    start=() 
    while 1: 
        (1) 
        print () 
        if ()-start>5: 
            break 
             
thread_=(target=foreverLoop) 
thread_.setDaemon(True) 
thread_.start() 
 
#The main thread waits, and the signal cannot be accepted.
#thread_.join() 
 
def _exitCheckfunc(): 
    print "ok" 
    try: 
        while 1: 
            alive=False 
            if thread_.isAlive(): 
                alive=True 
            if not alive: 
                break 
            (1)   
#In order to enable statistical time to run, you must capture KeyboardInterrupt :ctrl-c
    except KeyboardInterrupt, e: 
        traceback.print_exc() 
    print "consume time :",()-start 
         
threading._shutdown=_exitCheckfunc 

Disadvantages: Polling will always waste some CPU resources, and battery.

If there is a better solution, please suggest it.

ps1: Process monitoring solution:

Use another process to receive the signal and kill the execution process.

Copy the codeThe code is as follows:

# -*- coding: utf-8 -*- 
import time,signal,traceback,os 
import sys 
import threading 
start=() 
def foreverLoop(): 
    start=() 
    while 1: 
        (1) 
        print () 
        if ()-start>5: 
            break 
 
class Watcher: 
    """this class solves two problems with multithreaded
    programs in Python, (1) a signal might be delivered
    to any thread (which is just a malfeature) and (2) if
    the thread that gets the signal is waiting, the signal
    is ignored (which is a bug).
 
    The watcher is a concurrent process (not thread) that
    waits for a signal and the process that contains the
    threads.  See Appendix A of The Little Book of Semaphores.
    /semaphores/
 
    I have only tested this on Linux.  I would expect it to
    work on the Macintosh and not work on Windows.
    """ 
 
    def __init__(self): 
        """ Creates a child thread, which returns.  The parent
            thread waits for a KeyboardInterrupt and then kills
            the child thread.
        """ 
        = () 
        if == 0: 
            return 
        else: 
            () 
 
    def watch(self): 
        try: 
            () 
        except KeyboardInterrupt: 
            # I put the capital B in KeyBoardInterrupt so I can 
            # tell when the Watcher gets the SIGINT 
            print 'KeyBoardInterrupt' 
            () 
        () 
 
    def kill(self): 
        try: 
            (, ) 
        except OSError: pass 
 
Watcher()             
thread_=(target=foreverLoop) 
thread_.start() 

Note that watch() must be placed before thread creation, because the reason is unknown. . . . , otherwise it will end immediately