Friday, December 28, 2018

Multithreading (Introduction)

A thread is a sequence of instructions within a program that can be executed independently of other code. It is an entity within a process that can be scheduled for execution. Also, it is the smallest unit of processing that can be performed in an OS (Operating System). 

A thread contains all this information in a Thread Control Block (TCB):

Thread Identifier: Unique id (TID) is assigned to every new thread
Stack pointer: Points to thread’s stack in the process. Stack contains the local variables under thread’s scope.
Program counter: a register which stores the address of the instruction currently being executed by thread.
Thread state: can be running, ready, waiting, start or done.
Thread’s register set: registers assigned to thread for computations.
Parent process Pointer: A pointer to the Process control block (PCB) of the process that the thread lives on.

So far we have used a single thread programs and one such program is shown below:

import time

print('Start Thread1.')

time.sleep(5)

print('Sleeping time over, Thread1 stopped')


The output of this program is shown below:


Start Thread1.

Sleeping time over, Thread1 stopped

------------------
(program exited with code: 0)

Press any key to continue . . .

The above program prints Start Thread1, wait for 5 secs and then prints Sleeping time over, Thread1 stopped. This is because Python programs by default have a single thread of execution. A single threaded program has only one finger. But a multi threaded program has multiple fingers. Each finger still moves to the next line of code as defined by the flow  control statements, but the fingers can be at different places in the program, executing different lines of code at the same time.

Rather than having all of your code wait until the time.sleep() function finishes, you can execute the delayed or scheduled code in a separate thread using Python’s threading module. The separate thread will pause for the time.sleep calls. Meanwhile, your program can do other work in the original thread.

Let's modify our program as shown below:

import threading,time

print('Start Thread1.')

time.sleep(5)

print('Sleeping time over, Thread1 stopped')

print('Next part of program started')

def holdon():
  
    time.sleep(5)
    print('Thread which called function stopped')
  
obj = threading.Thread(target=holdon)
obj.start()

print('Next part of program ended')

The output of this program is shown below: 

Start Thread1.
Sleeping time over, Thread1 stopped
Next part of program started
Next part of program ended
Thread which called function stopped
------------------
(program exited with code: 0)

Press any key to continue . . .

To make a separate thread, you first need to make a Thread object by calling the threading.Thread() function. We define the holdon() function that we want to use in a new thread. To create a Thread object, we call threading.Thread() and pass it the keyword argument target=holdon which means the function we want to call in the new thread is holdon(). This is because you want to pass the holdon() function itself as the argument, not call holdon() and pass its return value. After we store the Thread object created by threading.Thread() in obj,we call obj.start() to create the new thread and start executing the target function in the new thread. 

As per our program Next part of program ended is the last print statement then it should be printed in the last in the output but instead it is printed second last. The reason Thread which called function stopped comes after it is that when obj.start() is called, the target function for obj is run in a new thread of execution. Think of it as a second finger appearing at the start of the holdon() function. The main thread continues to print('Next part of program ended.'). Meanwhile, the new thread that has been executing the time.sleep(5) call, pauses for 5 seconds. After it wakes from its 5-second wait, it prints 'Thread which called function stopped' and then returns from the holdon() function. Thus chronologically, 'Thread which called function stopped' is the last thing printed by the program. 

A Python program will not terminate until all its threads have terminated. When we ran our program, even though the original thread had terminated, the second thread was still executing the time.sleep(5) call hence the program which normally terminates when the last line of code in the file has run waited for the second thread to to terminate and then ended. 

Multiple threads can exist within one process where:
  • Each thread contains its own register set and local variables (stored in stack).
  • All thread of a process share global variables (stored in heap) and the program code.
Multiple threads can also cause problems called concurrency issues. These issues happen when threads read and write variables at the same time, causing the threads to trip over each other. Concurrency issues can be hard to reproduce consistently, making them hard to debug.

I've kept today's topic concise, we'll continue this topic in next posts, so until we meet keep practicing and learning Python as Python is easy to learn!






Share:

0 comments:

Post a Comment