Python threads for CPython and Pypi has a particular implementation because on the non thread-safe implementation of pyhton memory management.

Let’s start from Wikipedia:

In computer science, a thread of execution is the smallest sequence of programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system. The implementation of threads and processes differs between operating systems, but in most cases a thread is a component of a process. Multiple threads can exist within one process, executing concurrently and sharing resources such as memory, while different processes do not share these resources. In particular, the threads of a process share its executable code and the values of its variables at any given time.

 

We already said about the non thread-safe implementation of python, so before talking of threads in python we have to understand the global interpreter lock, also known as GIL.

GIL is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management (and Pypi too) is not thread-safe.

The GIL is controversial because it prevents multithreaded CPython programs from taking full advantage of multiprocessor systems in certain situations. Note that potentially blocking or long-running operations, such as I/O, image processing, and NumPy number crunching, happen outside the GIL. Therefore it is only in multithreaded programs that spend a lot of time inside the GIL, interpreting CPython bytecode, that the GIL becomes a bottleneck.

The above means that no two Python statements (bytecodes, strictly speaking) can execute at the same time. So this form of parallelization is only helpful if most of your threads are either not actively doing anything (for example, waiting for input), or doing something that happens outside the GIL (for example launching a subprocess or doing a numpy calculation)
This discussion however does not mean threads are not useful. Then, where are threads useful in Python?

  • In GUI applications to keep the UI thread responsive
  • IO tasks (network IO or filesystem IO)

Pyhton 3 threading modules.
There are two modules which support the usage of threads in Python:

  • thread
  • threading

The thread module has been deprecated, so we will discuss and use only the threading module.
The module “threading” is implemented in an object oriented way, i.e. every thread corresponds to an object.

As in other languages, there are many ways to use threads in Python.

  • workers
  • timers
  • daemons
  • threads

in this post we are going to explore worker threads and timers.

A worker thread is a Python thread that typically runs in the background and exists solely as a work horse to perform lengthy or blocking tasks.
A python timer is a subclass of Thread which provides the capability to execute a piece of code in background after a defined interval of time.

Let’s start with worker threads
First we have to implement a working function, this is a really simple functions it prints for 10 times a string passed as parameter.

def worker(worker_name, timing):
    for _ in range(10):
        print(worker_name)
        time.sleep(timing)

Then the main program will instantiate two threads


''' Worker threads sample code '''
import threading
import time

if __name__ == '__main__':
    TH1 = threading.Thread(target=worker, args=('TH1', 0.1,))
    TH2 = threading.Thread(target=worker, args=('TH2', 0.4,))
    TH1.start()
    TH2.start()
    TH1.join()
    TH2.join()

The out will be as follows:

$ python workers.py
TH1
TH2
TH1
TH1
TH1
TH2
TH1
TH1
TH1
TH1
TH2
TH1
TH1
TH2
TH2
TH2
TH2
TH2
TH2
TH2

As you can see, the two threads evolve simultaneously.

Python timers.
The best way to explain timers is by using a few lines of code.
The problems is as follows: we want to run a specific tast after 5 seconds since the start of the application

import threading

def timed_worker():
    print('timed_worker')

if __name__ == '__main__':
    TH1 = threading.Timer(5, timed_worker)
    TH1.start()

The first instruction instantiates a Timer that will run the timed_worker function after 5 seconds. The second line starts the timer. Really nothing more….
we can do a little bit more by passing arguments to the timer.

import threading

import threading

def timed_worker(timed_worker_name):
    print('timed_worker')

if __name__ == '__main__':
    TH1 = threading.Timer(5, timed_worker, args=('TW1',))
    TH1.start()

In this way you can run two timers and see what happens:
import threading

def timed_worker(timed_worker_name):
    print('timed_worker'+timed_worker_name)

if __name__ == '__main__':
    TW1 = threading.Timer(5, timed_worker, args=('TW1',))
    TW2 = threading.Timer(2, timed_worker, args=('TW2',))
    TW1.start()
    TW2.start()

As you can verify, the TW2 timer will run before the TW1

$ python timers.py
timed_workerTW2
timed_workerTW1

Extending python timers
python timers are Fire and Forget, if you want to repeat the action every interval seconds you have to extend the Timer class, the Timer implementation is in /Lib/threading.py, and the functionality is simple to obtain, modifying a single line of code of the run method.

from threading import Timer

class RepTimer(Timer):
    def run(self):
        while not self.finished.is_set():
            self.finished.wait(self.interval)
            self.function(*self.args, **self.kwargs)
        self.finished.set()

def timed_worker(timed_worker_name):
    print('timed_worker'+timed_worker_name)

if __name__ == '__main__':
    TW1 = RepTimer(0.1, timed_worker, args=('TW1',))
    TW1.start()

You can find full source code for this blog post at my github at timers.py and workers.py