SoFunction
Updated on 2024-11-21

A Deeper Understanding of Multithreading in Python A Must for Newbies

Example 1
We're going to request five different urls:
single-threaded

import time
import urllib2
 
defget_responses():
  urls=[
    ‘',
    ‘',
    ‘',
    ‘',
    ‘https://'
  ]
  start=()
  forurlinurls:
    printurl
    resp=(url)
    ()
  print”Elapsed time: %s”%(()-start)
 
get_responses()

The output is:
.com200
.com200
.com200
.com200
https://www.jb51.net200
Elapsed time:3.0814409256

Explanation:
The url is requested sequentially
Unless the cpu gets a response from one url, it doesn't go on to request the next url
Network requests can take a long time, so the cpu remains idle for the time it takes to wait for the return of the network request.
multi-threaded

import urllib2
import time
from threading import Thread
 
classGetUrlThread(Thread):
  def__init__(self, url):
    =url
    super(GetUrlThread,self).__init__()
 
  defrun(self):
    resp=()
    , ()
 
defget_responses():
  urls=[
    ‘',
    ‘',
    ‘',
    ‘',
    ‘https://'
  ]
  start=()
  threads=[]
  forurlinurls:
    t=GetUrlThread(url)
    (t)
    ()
  fortinthreads:
    ()
  print”Elapsed time: %s”%(()-start)
 
get_responses()

Output:
https://www.jb51.net200
.com200
.com200
.com200
.com200
Elapsed time:0.689890861511

Explanation:

Aware of the program's improvement in execution time
We have written a multithreaded program to reduce the waiting time of the cpu, when we are waiting for the return of a network request within one thread, at that time the cpu can switch to other threads to carry out the network request within other threads.
We expect one thread to handle one url, so when instantiating the thread class we pass a url.
Thread running means executing the run() method in the class.
In any case we want each thread to have to execute run().
Creating a thread for each url and calling the start() method tells the cpu to execute the run() method in the thread.
We want to count the time spent when all the threads have finished executing, so we call the join() method.
join() can notify the main thread to wait for this thread to finish before the next instruction can be executed.
For each thread we called the join() method, so we are calculating the runtime after all threads have executed.

About the thread:

The cpu may not execute the run() method immediately after calling start().
You can't determine the order in which run() is executed between thread builds.
For a separate thread, it is guaranteed that the statements in the run() method are executed in order.
This is because the in-thread url will be requested first, and then the returned result will be printed.

Example 2

We will use a program to demonstrate resource contention between multiple threads and fix the problem.

from threading import Thread
 
#define a global variable
some_var=0
 
classIncrementThread(Thread):
  defrun(self):
    #we want to read a global variable
    #and then increment it
    globalsome_var
    read_value=some_var
    print”some_var in %s is %d”%(, read_value)
    some_var=read_value+1
    print”some_var in %s after increment is %d”%(, some_var)
 
defuse_increment_thread():
  threads=[]
  foriinrange(50):
    t=IncrementThread()
    (t)
    ()
  fortinthreads:
    ()
  print”After 50 modifications, some_var should have become 50″
  print”After 50 modifications, some_var is %d”%(some_var,)
 
use_increment_thread()

Run this program multiple times and you will see multiple different results.
Explanation:
There is a global variable that all threads want to modify.
All threads should add 1 to this global variable.
There are 50 threads, and at the end this value should become 50, but it doesn't.
Why didn't you reach 50?
Thread t1 reads some_var when some_var is 15, and at this moment the cpu gives control to another thread, t2.
The t2 thread reads some_var also 15
Both t1 and t2 add some_var to 16
At the time, we were expecting two threads t1 t2 to make some_var + 2 into 17
Herein lies the competition for resources.
The same can happen between other threads, so there is a situation where the final result is less than 50.
Addressing competition for resources

from threading import Lock, Thread
lock=Lock()
some_var=0
 
classIncrementThread(Thread):
  defrun(self):
    #we want to read a global variable
    #and then increment it
    globalsome_var
    ()
    read_value=some_var
    print”some_var in %s is %d”%(, read_value)
    some_var=read_value+1
    print”some_var in %s after increment is %d”%(, some_var)
    ()
 
defuse_increment_thread():
  threads=[]
  foriinrange(50):
    t=IncrementThread()
    (t)
    ()
  fortinthreads:
    ()
  print”After 50 modifications, some_var should have become 50″
  print”After 50 modifications, some_var is %d”%(some_var,)
 
use_increment_thread()

Running this program again achieved the results we expected.
Explanation:
Lock is used to prevent competitive conditions
If thread t1 acquires a lock before performing some operation. Other threads will not perform the same operation until t1 releases the Lock
What we want to make sure is that once thread t1 has read some_var, no other thread can read some_var until t1 has finished modifying some_var
This way reading and modifying some_var becomes a logical atomic operation.
Example 3
Let's use an example to demonstrate that a thread cannot affect variables (non-global variables) within other threads.
() can make a thread hang and force a thread switch to occur.

from threading import Thread
import time
 
classCreateListThread(Thread):
  defrun(self):
    =[]
    foriinrange(10):
      (1)
      (i)
    
 
defuse_create_list_thread():
  foriinrange(3):
    t=CreateListThread()
    ()
 
use_create_list_thread()

After running it a few times I realized that it wasn't printing the results it was striving for. While one thread was printing, the cpu switched to another thread, so incorrect results were produced. We need to make sure that print is a logical atomic operation in case the print is interrupted by another thread.
We used Lock(), see the example below.

from threading import Thread, Lock
import time
 
lock=Lock()
 
classCreateListThread(Thread):
  defrun(self):
    =[]
    foriinrange(10):
      (1)
      (i)
    ()
    
    ()
 
defuse_create_list_thread():
  foriinrange(3):
    t=CreateListThread()
    ()
 
use_create_list_thread()

This time we see the correct result. It is proved that a thread cannot modify variables inside other threads (non-global variables).