Skip to content

Instantly share code, notes, and snippets.

@kenjyco
Created February 20, 2015 06:57
Show Gist options
  • Save kenjyco/01ac345bf6c6e44577b1 to your computer and use it in GitHub Desktop.
Save kenjyco/01ac345bf6c6e44577b1 to your computer and use it in GitHub Desktop.
A relatively simple script that demonstrates threaded daemons, signal handling, cleanup functions, and logging with the Python standard library.

Run the script

% python ./sleepy_random_threads.py
2015-02-20 00:38:23,547: 'a' created with a sleep time of 19 seconds
2015-02-20 00:38:23,548: 'c' created with a sleep time of 5 seconds
2015-02-20 00:38:23,548: 'b' created with a sleep time of 2 seconds
2015-02-20 00:38:23,550: 'e' created with a sleep time of 7 seconds
2015-02-20 00:38:23,550: 'd' created with a sleep time of 11 seconds
2015-02-20 00:38:23,551: 'g' created with a sleep time of 18 seconds
2015-02-20 00:38:23,551: 'f' created with a sleep time of 22 seconds
  • Seven threads (named "a" through "g") are created and executed as daemons.
    • each thread will sleep between 2 and 22 seconds
    • sleep duration is determined before the thread is created
    • each thread will emit an INFO message when it is created, telling the thread name and the sleep duration
  • The threads will emit a pair of DEBUG messages and go to sleep for their specified durations (before repeating)
    • one message for the thread name and sleep duration
    • one message for the thread name and the number of completed sleep cycles
    • the DEBUG messages are written to the log file, but not the console

Stop the script (press ctrl + c)

^C2015-02-20 00:39:02,539: Signal received to stop this script. Exiting gracefully.
2015-02-20 00:39:02,539: 'f' completed 1 sleep cycles.
2015-02-20 00:39:02,540: 'g' completed 2 sleep cycles.
2015-02-20 00:39:02,540: 'd' completed 3 sleep cycles.
2015-02-20 00:39:02,540: 'e' completed 5 sleep cycles.
2015-02-20 00:39:02,541: 'b' completed 19 sleep cycles.
2015-02-20 00:39:02,541: 'c' completed 7 sleep cycles.
2015-02-20 00:39:02,541: 'a' completed 2 sleep cycles.
2015-02-20 00:39:02,541: Everything finished.
  • When an exit signal is captured, the signal handler will emit a WARNING message then script will begin to exit
  • When each thread stops, it will emit an INFO message telling the thread name and the number of completed sleep cycles
  • When all theads have stopped, the script will emit an "everything finished" INFO message

Note: you can also kill the PID of the script in another window.

View the log output

Use j and k to scroll down and up. Use q to quit.

% less -FX log_random_threads.log
2015-02-20 00:38:23,547 - INFO - __init__: 'a' created with a sleep time of 19 seconds
2015-02-20 00:38:23,548 - DEBUG - run: 'a' is sleeping for 19 seconds.
2015-02-20 00:38:23,548 - DEBUG - run: 'a' completed 0 sleep cycles.
2015-02-20 00:38:23,548 - INFO - __init__: 'c' created with a sleep time of 5 seconds
2015-02-20 00:38:23,548 - DEBUG - run: 'c' is sleeping for 5 seconds.
2015-02-20 00:38:23,548 - INFO - __init__: 'b' created with a sleep time of 2 seconds
2015-02-20 00:38:23,549 - DEBUG - run: 'c' completed 0 sleep cycles.
2015-02-20 00:38:23,550 - DEBUG - run: 'b' is sleeping for 2 seconds.
2015-02-20 00:38:23,550 - INFO - __init__: 'e' created with a sleep time of 7 seconds
2015-02-20 00:38:23,550 - DEBUG - run: 'b' completed 0 sleep cycles.
...

Notice that the log file has a different format than the console output.

Official docs

import atexit
import logging
import random
import signal
import sys
import threading
import time
LOGFILE = 'log_random_threads.log'
# Setup logger (overwrite log every time with filemode='w')
logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(funcName)s: %(message)s',
level=logging.DEBUG,
filename=LOGFILE,
filemode='w')
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s: %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
def all_done():
logging.info('Everything finished.')
# Register the `all_done` function first, so it will be called last
atexit.register(all_done)
class Blah(object):
"""No, this class should not be inheriting from threading.Thread.
"""
def __init__(self, name, seconds):
self.name = name
self.seconds = int(seconds)
self.count = 0
logging.info('{0} created with a sleep time of {1} seconds'.format(
repr(self.name),
self.seconds))
# Register the `goodbye` method to be run when class instance is done
atexit.register(self.goodbye)
def run(self):
while True:
logging.debug('{0} is sleeping for {1} seconds.'.format(
repr(self.name),
self.seconds))
logging.debug('{0} completed {1} sleep cycles.'.format(
repr(self.name),
self.count))
time.sleep(self.seconds)
self.count += 1
def goodbye(self):
logging.info('{0} completed {1} sleep cycles.'.format(
repr(self.name),
self.count))
if __name__ == '__main__':
print 'Logging extra DEBUG output to "{}"'.format(LOGFILE)
print 'Use <ctrl>+c to kill the script.\n'
def signal_handler(signal, frame):
"""
See: http://stackoverflow.com/questions/1112343
http://stackoverflow.com/questions/930519
"""
logging.warning('Signal received to stop this script. Exiting gracefully.')
sys.exit(0)
for sig in (signal.SIGABRT, signal.SIGILL, signal.SIGINT, signal.SIGTERM):
signal.signal(sig, signal_handler)
# Generate a dictionary where the keys are in the provided list and the
# values are random integers between 2 and 22
d = { k: random.randint(2,22) for k in ['a', 'b', 'c', 'd', 'e', 'f', 'g'] }
# Create the threads and start them
for name, seconds in d.iteritems():
blah = Blah(name, seconds)
t = threading.Thread(target=blah.run)
t.daemon=True
t.start()
# Don't delete this while loop
# - See: http://stackoverflow.com/questions/3788208/
while True:
time.sleep(5)
# This will never get executed because when this script is killed or
# interrupted, only functions/methods registered with atexit will
# get called
logging.info('This will never be added to the log.')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment