Monday, 24 April 2017

Multi Threading

Multi Threading:

Processor:
  • processor is a electronic circuit, it understands only power signals,they are:
ON  ---> 1
OFF ---> 0
  • electronic circuit never executes multiple statements/process/applications at a time,it executes one after another.
Thread:
  • Thread is a functionality/logic which executes simultaneously along with the other part of the program. 
                                               (or)
  • Thread is a light-weight process.
  • Executing the statements one with another(not completely parallel) some part of the program execution.
Process:
  • Any program which is under execution is known as process.
Note:
  • upto python2.4, high level threading concepts are not implemented.
  • after python2.4, high level threading concepts are implemented similar to java and .net programming threading concepts.
Defining the functionality as a Thread:-

  • We can define the functionality as a Thread by overriding    run( ) method of Thread class.
  • Thread is a pre-defined class, which is defining Threading module.

import threading

class X(threading.Thread):

    def run(self):

        for p in range(10):
            print(p)
x1=X( )
x1.start( )
for z in range(10,20):
    print(z)

010

111

212

313

414

515

616

717

818

919

  • If we call the run( ) method directly it will be executed as a normal method.
  • In-order to execute the run( ) method logic as a Thread we have to call the run( ) method through the start( ) method of Thread class.
  • By default python interpreter creates one Thread that is main( ) Thread.

The threading module exposes all the methods of the thread module and provides some additional methods:
  • threading.activeCount( ): Returns the number of thread objects that are active.
  • threading.currentThread( ): Returns the number of thread objects in the caller's thread control.
  • threading.enumerate( ): Returns a list of all thread objects that are currently active.

The methods provided by the Thread class are as follows:
  • run( ): The run() method is the entry point for a thread.
  • start( ): The start() method starts a thread by calling the run method.
  • join([time]): The join() waits for threads to terminate.
  • isAlive( ): The isAlive() method checks whether a thread is still executing.
  • getName( ): The getName() method returns the name of a thread.
  • setName( ): The setName() method sets the name of a thread.


import threading

class X(threading.Thread):

    def run(self):
        for p in range(10):
            print(p)
class Y(threading.Thread):
    def run(self):
       for q in range(10,20):
            print(q) 
x1=X( )
x1.start( )
y1=Y( )
y1.start( )

suspending the execution of the thread temporary:
  • we can suspend the execution of the thread temporarily for some time by calling sleep( ) function of time module.

 import threading

import time
class x(threading.Thread):
    def run(self):
        time.sleep(1)
        for p in range(1,10):
            print(p)
class y(threading.Thread):
    def run(self):
        for p in range(11,20):
            print(p)
t1=x( )
t1.start( )
t2=y( )
t2.start( )
print("in main thread")

Note:
  • at the same time multiple threads access the same function, in this case deadlock is occurred.
  • to over come this problem,we are going to use the concept called synchronization.

synchronization:

  • the concept of avoiding the multiple threads to access the same functionality at a time is known as a synchronization.
  • Thread synchronization is defined as a mechanism which ensures that two or more concurrent processes or threads do not simultaneously execute some particular program segment known as critical section. Processes' access to critical section is controlled by using synchronization techniques. 
  • When one thread starts executing the critical section (serialized segment of the program) the other thread should wait until the first thread finishes.
  • If proper synchronization techniques are not applied, it may cause a race condition.
  • For example, suppose that there are three processes, namely 1, 2, and 3. All three of them are concurrently executing, and they need to share a common resource (critical section) as shown in Figure 1. Synchronization should be used here to avoid any conflicts for accessing this shared resource. Hence, when Process 1 and 2 both try to access that resource, it should be assigned to only one process at a time. If it is assigned to Process 1, the other process (Process 2) needs to wait until Process 1 frees that resource (as shown in Figure 2).

  • another synchronization requirement which needs to be considered is the order in which particular processes or threads should be executed. For example, we cannot board a plane until we buy a ticket. Similarly, we cannot check e-mails without validating our credentials (i.e., user name and password). In the same way, an ATM will not provide any service until we provide it with a correct PIN.
  • Other than mutual exclusion, synchronization also deals with the following:
    • deadlock: which occurs when many processes are waiting for a shared resource (critical section) which is being held by some other process. In this case, the processes just keep waiting and execute no further.
    • starvation:which occurs when a process is waiting to enter the critical section, but other processes monopolize the critical section, and the first process is forced to wait indefinitely.
    synchronization primitives, including semaphores, condition variables, events, and locks.
  • A semaphore is a very general synchronization primitive, and most other synchronization operations can be reduced to semaphore operations.

    Semaphores are in most cases too basic an abstraction to be used effectively. There are a few simple problems that are best solved with semaphores, but in general locks and condition variables are a much better abstraction.
  • A condition variable is an object used in combination with its associated lock to allow a thread to wait for some condition while it is inside a critical section. Only a thread holding the associated lock is allowed to use a condition variable associated with that lock. A condition variable has only one associated lock, but multiple condition variables may be associated with a single lock.
  • A lock is an object that can be held by at most one thread at a time. Only the thread that last acquired a lock is allowed to release that lock. Locks are useful for guarding critical sections. Keep in mind that multiple methods can be part of the same critical section.


  • we can implement the synchronization by using locking concept.
  • we can get the lock object by calling lock( ) function of threading module.
  • acquire the lock by using acquire( ) method.
  • release the lock by using release( ) method.
  • if any thread acquire the lock an any logic other threads cannot access same logic until lock is released by the other thread.



import threading

import time

class thread1(threading.Thread):
    def run(self):
        threadLock.acquire()
        f1("python")
        threadLock.release()
class thread2(threading.Thread):
    def run(self):
        threadLock.acquire()
        f1("hadoop")
        threadLock.release()
def f1(x):
    print("hello",x)
    time.sleep(5)
    print("world")
threadLock=threading.Lock()
t1=thread1()
t1.start()
t2=thread2()
t2.start()

suspending the execution of threads until execution of another thread:

  • we can suspend the execution of current thread until execution of specified threads are over by calling join( ) method Thread class.


import threading

import time
class thread1(threading.Thread):
    def run(self):
        threadLock.acquire()
        f1("python")
        threadLock.release()
class thread2(threading.Thread):
    def run(self):
        threadLock.acquire()
        f1("hadoop")
        threadLock.release()
def f1(x):
    print("hello",x)
    time.sleep(5)
    print("world")
threadLock=threading.Lock()
threads=[]
t1=thread1()
t1.start()
t2=thread2()
t2.start()
threads.append(t1)
threads.append(t2)
for t in threads:
    t.join()
print("end of the main thread")

Example:

import threading
import datetime

class ThreadClass(threading.Thread):
  def run(self):
    now = datetime.datetime.now()
    print "%s says Hello World at time: %s" %(self.getName(), now)

for i in range(2):
  t = ThreadClass()

  t.start()


Using queues with threads:

  • threading can be complicated when threads need to share data or resources. 
  • The threading module does provide many synchronization primitives, including semaphores, condition variables, events, and locks.
  • While these options exist, it is considered a best practice to instead concentrate on using queues. 
  • Queues are much easier to deal with, and make threaded programming considerably safer, as they effectively funnel all access to a resource to a single thread, and allow a cleaner and more readable design pattern.


Basic FIFO Queue:



  • The Queue class implements a basic first-in, first-out container. 
  • Elements are added to one “end” of the sequence using put( ), and removed from the other end using get( ).

import Queue

q = Queue.Queue( )

for i in range(5):
    q.put(i)

while not q.empty( ):
    print q.get( )


LIFO Queue


  • The item most recently put( ) into the queue is removed by  get( ).

import Queue

q = Queue.LifoQueue()

for i in range(5):
    q.put(i)

while not q.empty( ):
    print q.get( )


Priority Queue:

import Queue

class Job(object):
    def __init__(self, priority, description):
        self.priority = priority
        self.description = description
        print 'New job:', description
        return
    def __cmp__(self, other):
        return cmp(self.priority, other.priority)

q = Queue.PriorityQueue()

q.put( Job(3, 'Mid-level job') )
q.put( Job(10, 'Low-level job') )
q.put( Job(1, 'Important job') )

while not q.empty():
    next_job = q.get()
    print 'Processing job:', next_job.description



Example:

from Queue import Queue
from threading import Thread

def do_stuff(q):
  while True:
    print q.get()
    q.task_done()

q = Queue(maxsize=0)
num_threads = 10

for i in range(num_threads):
  worker = Thread(target=do_stuff, args=(q,))
  worker.setDaemon(True)
  worker.start()

for x in range(100):
  q.put(x)

q.join()


No comments:

Post a Comment