Python 3 multithreading

Python 3 Multithreading

Multithreading is similar to executing multiple programs simultaneously. Multithreading has the following advantages:

  • Using threads, you can offload long-running program tasks to the background.
  • The user interface can be more appealing. For example, when a user clicks a button to trigger the processing of an event, a progress bar can pop up to indicate the progress.
  • The program may run faster.
  • Threads are particularly useful for waiting tasks such as user input, file reading and writing, and network data transmission. In these cases, we can free up precious resources such as memory.

Each independent thread has a program entry point, a sequential execution sequence, and a program exit. However, threads cannot execute independently and must reside within the application, which provides execution control for multiple threads.

Each thread has its own set of CPU registers, called the thread context, which reflects the state of the CPU registers when the thread last executed.

The instruction pointer and stack pointer registers are the two most important registers in the thread context. Threads always execute in the process context, and these addresses are used to identify memory in the address space of the process that owns the thread.

  • Threads can be preempted (interrupted).
  • While other threads are running, a thread can be temporarily suspended (also called sleeping)—this is known as yielding.

Threads can be categorized as:

  • Kernel threads:Created and destroyed by the operating system kernel.
  • User threads:Threads implemented in user programs without kernel support.

The two commonly used modules for Python 3 threads are:

  • _thread
  • threading (recommended)

The thread module has been deprecated. Users can use the threading module instead. Therefore, the “thread” module cannot be used in Python 3. For compatibility, Python 3 renamed thread to “_thread”.

Learning Python Threads

There are two ways to use threads in Python: using a function or wrapping a thread object with a class.

Functional: Call the start_new_thread() function in the _thread module to create a new thread. The syntax is as follows:

_thread.start_new_thread (function, args[, kwargs])

Parameter Description:

  • function – The thread function.
  • args – The arguments passed to the thread function, which must be a tuple type.
  • kwargs – Optional parameters.

Example

#!/usr/bin/python3<br>
<br>
import _thread<br>
import time<br>
<br>
# Define a function for a thread<br>
def print_time( threadName, delay):<br>
   count = 0<br>
   while count < 5:<br>
      time.sleep(delay)<br>
      count += 1<br>
      print ("%s: %s" % ( threadName, time.ctime(time.time()) ))<br>
<br>
# Create two threads<br>
try:<br>
   _thread.start_new_thread( print_time, ("Thread-1", 2, ) )<br>
_thread.start_new_thread( print_time, ("Thread-2", 4, ) )<br>
except:<br>
   print ("Error: Unable to start thread")<br>
<br>
while 1:<br>
   pass<br>

Executing the above program will produce the following output:

Thread-1: Wed Apr 6 11:36:31 2016
Thread-1: Wed Apr 6 11:36:33 2016
Thread-2: Wed Apr 6 11:36:33 2016
Thread-1: Wed Apr 6 11:36:35 2016
Thread-1: Wed Apr 6 11:36:37 2016
Thread-2: Wed Apr 6 11:36:37 2016
Thread-1: Wed Apr 6 11:36:39 2016
Thread-2: Wed Apr 6 11:36:41 2016
Thread-2: Wed Apr 6 11:36:45 2016
Thread-2: Wed Apr 6 11:36:49 2016

After executing the above program, you can press ctrl-c to exit.


Threading Module

Python 3 provides thread support through two standard libraries: _thread and threading.

_thread provides low-level, primitive threading and a simple lock. Its functionality is relatively limited compared to the threading module.

In addition to all the methods in the _thread module, the threading module also provides the following methods:

  • threading.currentThread(): Returns the current thread variable.
  • threading.enumerate(): Returns a list of currently running threads. Currently running refers to threads that have been started and have not yet terminated, excluding threads that have been started or terminated.
  • threading.activeCount(): Returns the number of currently running threads, which is the same as len(threading.enumerate()).

In addition to the usage methods, the threading module also provides the Thread class for working with threads. The Thread class provides the following methods:

  • run(): A method used to indicate thread activity.
  • start(): Starts thread activity.
  • join([time]): Waits until a thread terminates. This blocks the calling thread until the thread’s join() method is called, either exiting normally or throwing an unhandled exception, or the optional timeout occurs.
  • isAlive(): Returns whether the thread is alive.
  • getName(): Returns the thread’s name.
  • setName(): Sets the thread’s name.

Creating Threads Using the threading Module

We can create a new subclass directly from threading.Thread and call the start() method after instantiating it to start a new thread. This calls the thread’s run() method:

Example

#!/usr/bin/python3<br>
<br>
import threading<br>
import time<br>
<br>
exitFlag = 0<br>
<br>
class myThread (threading.Thread):<br>
    def __init__(self, threadID, name, counter):<br>
        threading.Thread.__init__(self)<br>
        self.threadID = threadID<br>
        self.name = name<br>
        self.counter = counter<br>
def run(self):<br>
                print ("Start thread: " + self.name)<br>
            print_time(self.name, self.counter, 5)<br>
              print ("Exit thread: " + self.name)<br>
<br>
def print_time(threadName, delay, counter):<br>
while counter:<br>
                          if exitFlag:<br>
                threadName.exit()<br>
time.sleep(delay)<br>
            print ("%s: %s" % (threadName, time.ctime(time.time())))<br>
counter -= 1<br>
<br>
#Create new thread<br>
thread1 = myThread(1, "Thread-1", 1)<br>
thread2 = myThread(2, "Thread-2", 2)<br>
<br>
# Start a new thread<br>
thread1.start()<br>
thread2.start()<br>
thread1.join()<br>
thread2.join()<br>
print ("Exiting main thread")<br>

The above program execution results are as follows;

Starting thread: Thread-1
Starting thread: Thread-2
Thread-1: Wed Apr 6 11:46:46 2016
Thread-1: Wed Apr 6 11:46:47 2016
Thread-2: Wed Apr 6 11:46:47 2016
Thread-1: Wed Apr 6 11:46:48 2016
Thread-1: Wed Apr 6 11:46:49 2016
Thread-2: Wed Apr 6 11:46:49 2016
Thread-1: Wed Apr 6 11:46:50 2016
Exiting thread: Thread-1
Thread-2: Wed Apr 6 11:46:51 2016
Thread-2: Wed Apr 6 11:46:53 2016
Thread-2: Wed Apr 6 11:46:55 2016
Exiting thread: Thread-2
Exiting main thread

Thread Synchronization

If multiple threads modify data simultaneously, unpredictable results may occur. To ensure data accuracy, multiple threads must be synchronized.

Simple thread synchronization can be achieved using the Thread object’s Lock and Rlock methods. Both objects have acquire and release methods. For data that only one thread should be able to access at a time, operations can be placed between the acquire and release methods. As shown below:

The advantage of multithreading is that it can run multiple tasks simultaneously (at least that’s how it feels). However, when threads need to share data, data desynchronization can occur.

Consider this scenario: a list contains all 0 elements. Thread “set” changes all elements to 1 from the back, while thread “print” reads and prints the list from the front.

Then, by the time thread “set” begins changing the list, thread “print” might start printing the list, resulting in half 0s and half 1s in the output. This is data desynchronization. To avoid this situation, the concept of locks is introduced.

Locks have two states: locked and unlocked. Whenever a thread, such as “set,” wants to access shared data, it must first acquire a lock. If another thread, such as “print,” already has the lock, thread “set” is paused, effectively blocking the access. After thread “print” completes its access and releases the lock, thread “set” can resume.

This approach ensures that when printing a list, the output is either all 0s or all 1s, eliminating the awkward situation of half 0s and half 1s.

Examples

#!/usr/bin/python3<br>
<br>
import threading<br>
import time<br>
<br>
class myThread (threading.Thread):<br>
def __init__(self, threadID, name, counter):<br>
        threading.Thread.__init__(self)<br>
           self.threadID = threadID<br>
         self.name = name<br>
self.counter = counter<br>
def run(self):<br>
                  print ("Start thread: " + self.name)<br>
​ ​ ​ #Acquire locks for thread synchronization<br>
threadLock.acquire()<br>
            print_time(self.name, self.counter, 3)<br>
        # Release the lock and start the next thread<br>
        threadLock.release()<br>
<br>
def print_time(threadName, delay, counter):<br>
    while counter:<br>
        time.sleep(delay)<br>
        print ("%s: %s" % (threadName, time.ctime(time.time())))<br>
        counter -= 1<br>
<br>
threadLock = threading.Lock()<br>
threads = []<br>
<br>
# Create a new thread<br>
thread1 = myThread(1, "Thread-1", 1)<br>
thread2 = myThread(2, "Thread-2", 2)<br>
<br>
# Start a new thread<br>
thread1.start()<br>
thread2.start()<br>
<br>
# Add a thread to the thread list<br>
threads.append(thread1)<br>
threads.append(thread2)<br>
<br>
# Wait for all threads to complete<br>
for t in threads:<br>
    t.join()<br>
print ("Exiting main thread")<br>

Executing the above program will output:

Starting threads: Thread-1
Starting threads: Thread-2
Thread-1: Wed Apr 6 11:52:57 2016
Thread-1: Wed Apr 6 11:52:58 2016
Thread-1: Wed Apr 6 11:52:59 2016
Thread-2: Wed Apr 6 11:53:01 2016
Thread-2: Wed Apr 6 11:53:03 2016
Thread-2: Wed Apr 6 11:53:05 2016
Exiting the main thread

Thread Priority Queue

Python’s Queue module provides synchronized, thread-safe queue classes, including FIFO (First-In-First-Out) queues (Queue), LIFO (Last-In-First-Out) queues (LifoQueue), and PriorityQueue.

These queues implement locking primitives and can be used directly in multithreading. Queues can be used to synchronize threads.

Common methods in the Queue module:

  • Queue.qsize() Returns the size of the queue
  • Queue.empty() Returns True if the queue is empty, otherwise False
  • Queue.full() Returns True if the queue is full, otherwise False
  • Queue.full corresponds to the maxsize value
  • Queue.get([block[, timeout]]) Gets the queue, with a timeout waiting time
  • Queue.get_nowait() Equivalent to Queue.get(False)
  • Queue.put(item) Writes to the queue, with a timeout waiting time
  • Queue.put_nowait(item) Equivalent to Queue.put(item, False)
  • Queue.task_done() After completing a task, the Queue.task_done() function sends a signal to the queue indicating that the task has been completed.

    Queue.join() actually means waiting until the queue is empty before performing other operations.

Example

#!/usr/bin/python3<br>
<br>
import queue<br>
import threading<br>
import time<br>
<br>
exitFlag = 0<br>
<br>
class myThread (threading.Thread):<br>
    def __init__(self, threadID, name, q):<br>
        threading.Thread.__init__(self)<br>
        self.threadID = threadID<br>
        self.name = name<br>
        self.q = q<br>
    def run(self):<br>
                print ("Start thread: " + self.name)<br>
Process_data(self.name, self.q)<br>
              print ("Exit thread: " + self.name)<br>
<br>
def process_data(threadName, q):<br>
while not exitFlag:<br>
queueLock.acquire()<br>
If not workQueue.empty():<br>
               data = q.get()<br>
queueLock.release()<br>
                      print ("%s processing %s" % (threadName, data))<br>
        else:<br>
queueLock.release()<br>
time.sleep(1)<br>
<br>
threadList = ["Thread-1", "Thread-2", "Thread-3"]<br>
nameList = ["One", "Two", "Three", "Four", "Five"]<br>
queueLock = threading.Lock()<br>
workQueue = queue.Queue(10)<br>
threads = []<br>
threadID = 1<br>
<br>
#Create new thread<br>
for tName in threadList:<br>
thread = myThread(threadID, tName, workQueue)<br>
thread.start()<br>
Threads.append(thread)<br>
threadID += 1<br>
<br>
# Fill the queue<br>
queueLock.acquire()<br>
for word in nameList:<br>
workQueue.put(word)<br>
queueLock.release()<br>
<br>
# Wait for the queue to clear<br>
while not workQueue.empty():<br>
    pass<br>
<br>
# Notify the thread that it's time to exit<br>
exitFlag = 1<br>
<br>
# Wait for all threads to complete<br>
for t in threads:<br>
    t.join()<br>
print ("Exiting main thread")<br>

Execution results of the above program:

Starting thread: Thread-1
Starting thread: Thread-2
Starting thread: Thread-3
Thread-3 processing One
Thread-1 processing Two
Thread-2 processing Three
Thread-3 processing Four
Thread-1 processing Five
Exiting thread: Thread-3
Exiting thread: Thread-2
Exiting thread: Thread-1
Exiting main thread

Leave a Reply

Your email address will not be published. Required fields are marked *