Threads

Threading is a method to create multiple paths of execution inside of an specific application. All applications have at least one main thread, this is a requirement for be executed by a processor. When you have just one thread, it will be responsible to do all tasks within the application (update the user interface, perform computations, respond to events, etc).

What happen when there is a high time consuming job that needs to be done? As the thread is also responsible to update the user interface, the user may think that the application has crashed, restart it or get frustrated for not getting a response.

To deal with these type of problems, we can define several threads within our application that will allow us to run time consuming tasks in different execution paths, releasing the main thread so it can respond to the user.

Python offers a very flexible and easy to use framework to deal with threads:


import threading

def background():
    [code]

t_1 = threading.Thread(name='t_1', target=background)
t_1.start()
    
Docs

Read the python threading documents

Basically, you need to create a Thread object with at least two parameters: name and target. Name specifies a way to identify the Thread (this information is useful when there is an exception and we must figure out what is causing it). Target simply tells to the thread which method should execute. Then, with the method Start() the execution begins in a separate path.

If the method that we want to execute has parameters, we can specify them via kwards thread parameter. We have to create a dictionary that contains the parameters' name as keys in String type. The values of those keys should be the corresponding value that we want to pass to the method that will be executed by the thread (values can be any type, not necessarily strings).


import threading

def background(username, password):
    [code]

user = 'test'
pwd = 'test.123'
t_1 = threading.Thread(name='t_1', target=background, kwargs={'username' : user, 'password' : pwd)
t_1.start()
    

Synchronization

Thread synchronization is a mechanism that ensures that two or more threads do not access or modify a shared resource (e.g. a variable or a database connection) at the same time. Overlapping access from multiple threads may result in bugs that are usually present under heavy application load situations (usually in production!!).

Locks are a fundamental synchronization mechanism provided by the threading module. A lock can be held by a single thread (locked), or by no thread at all (unlocked). When a thread t_1 attempts to hold a lock that is held by some other thread t_2, execution of the t_1 is paused until the lock is released by t_2.


lock = threading.Lock()

def db_write():
    lock.acquire()
    [access to database]
    lock.release()

t_1 = threading.Thread(name='t_1', target=db_write)
t_1.start()
t_2 = threading.Thread(name='t_2', target=db_write)
t_2.start()
    

In the previous example there are two threads that target to the method db_write() that accesses to a database that should not be writen by two different processes at the same time. Before access to the share resource (the database), the call to lock.acquire() holds the lock (this will wait for the lock to be released, if necessary) and after the shared operations were made, a call to lock.release() releases it for other threads that need access as well.