Python Multiprocessing Tutorial - Python Tutorial | Logic Practice

Python Multiprocessing Tutorial

In Python, multiprocessing refers to the system's capacity to execute multiple processes concurrently. In straightforward terms, multiprocessing utilizes two or more CPUs within a single computer system. This approach also has the capability to distribute tasks among several processes.

Processing units utilize the primary memory and peripheral devices to execute programs concurrently. A multiprocessing application is divided into smaller components that operate autonomously. The operating system assigns each process to a specific processor.

Python includes a built-in package known as multiprocessing that facilitates process swapping. Prior to utilizing multiprocessing, it is essential to understand the concept of the process object.

Why Multiprocessing?

Multiprocessing is crucial for executing multiple tasks simultaneously within a computer system. Consider a computer that lacks multiprocessing capabilities or operates with a single processor. In this scenario, we allocate different processes to the system concurrently.

Subsequently, it must pause the ongoing task and transition to a different one to ensure all operations continue smoothly. This can be likened to a chef working solo in a kitchen. He must perform various duties to prepare meals, including chopping, sanitizing, cooking, kneading dough, and baking, among others.

Consequently, multiprocessing is crucial for executing multiple tasks simultaneously without disruption. It also simplifies the monitoring of all tasks involved. This is the reason why the idea of multiprocessing emerged.

  • Multiprocessing can be depicted as a computer equipped with multiple central processors.
  • A multi-core processor denotes a single computing unit that contains two or more autonomous cores.

In multiprocessing, the CPU is capable of allocating several tasks simultaneously, with each task being managed by its dedicated processor.

Multiprocessing In Python

Python features the multiprocessing module, which enables the execution of several tasks concurrently on a single machine. It presents a straightforward and easy-to-use API for engaging with multiprocessing.

Python Multiprocessing Example

Let us examine a straightforward illustration of multiprocessing in Python.

Example

from multiprocessing import Process

   def disp():

      print ('Hello !! Welcome to Python Tutorial')

      if __name__ == '__main__':

      p = Process(target=disp)

      p.start()

      p.join()

Output:

Output

'Hello !! Welcome to Python Tutorial'

Explanation:

In the code provided, the Process class has been imported, and a Process object is instantiated within the disp function. Subsequently, the process is initiated using the start method and concluded with the join method. Additionally, we can pass arguments to the defined function by utilizing the args keyword.

Python Multprocessing Example with Arguments

Let’s examine the subsequent illustration of multiprocessing involving arguments.

Example

# Python multiprocessing example

# importing the multiprocessing module

import multiprocessing

def cube(n):

   # This function will print the cube of the given number

   print("The Cube is: {}".format(n * n * n))

def square(n):

    # This function will print the square of the given number

   print("The Square is: {}".format(n * n))

if __name__ == "__main__":

   # creating two processes

   process1 = multiprocessing.Process(target= square, args=(5, ))

   process2 = multiprocessing.Process(target= cube, args=(5, ))

   # Here we start the process 1

   process1.start()

   # Here we start process 2

   process2.start()

   # The join() method is used to wait for process 1 to complete

   process1.join()

   # It is used to wait for process 1 to complete

   process2.join()

   # Print if both processes are completed

   print("Both processes are finished")

Output:

Output

The Cube is: 125

The Square is: 25

Both processes are finished

Explanation -

In the preceding example, we developed two functions: the cube function, which computes the cube of a specified number, and the square function, which determines the square of the specified number.

Subsequently, we instantiated the process object from the Process class, which takes two parameters. The first parameter, target, signifies the function intended for execution, while the second parameter, args, denotes the arguments to be supplied to the function.

Example

process1 = multiprocessing.Process(target= square, args=(5, ))

process2 = multiprocessing.Process(target= cube, args=(5, ))

The process has been initiated using the start method.

Example

process1.start()

process2.start()

The output demonstrates that it pauses until process one is completed, followed by process two. The final statement is executed only after both processes have concluded.

Python Multiprocessing Classes

The Python multiprocessing module offers various classes that are frequently utilized for developing parallel applications. This discussion will focus on its key classes: Process, Queue, and Lock. The Process class was covered in the earlier example, and now we will turn our attention to the Queue and Lock classes.

Python Multiprocessing Classes Example

Here is a straightforward illustration of how to retrieve the count of CPUs present in the system.

Example

import multiprocessing

print("The number of CPU currently working in system : ", multiprocessing.cpu_count())

Output:

Output

('The number of CPU currently woking in system : ', 32)

The quantity of CPUs may differ for your personal computer. In our case, we have 32 cores.

Python Multiprocessing Using Queue Class

It is understood that the Queue is a vital component of data structures. In Python multiprocessing, the Queue functions similarly to the data structure queue, adhering to the "First-In-First-Out" principle. Typically, the Queue holds Python objects and is crucial for facilitating data sharing among processes.

Queues are provided as arguments in the target function of the Process to enable the process to access data. The Queue offers the put method for adding data and the get method for retrieving data from the queues.

Python Multiprocessing Example using Queue Class

To illustrate the concept of multiprocessing with the Queue class in Python, we will consider an example.

Example

# Importing Queue Class

from multiprocessing import Queue

fruits = ['Apple', 'Orange', 'Guava', 'Papaya', 'Banana']

count = 1

# creating a queue object

queue = Queue()

print('pushing items to the queue:')

for fr in fruits:

    print('item no: ', count, ' ', fr)

    queue.put(fr)

    count += 1

print('\npopping items from the queue:')

count = 0

while not queue.empty():

    print('item no: ', count, ' ', queue.get())

    count += 1

Output:

Output

pushing items to the queue:

('item no: ', 1, ' ', 'Apple')

('item no: ', 2, ' ', 'Orange')

('item no: ', 3, ' ', 'Guava')

('item no: ', 4, ' ', 'Papaya')

('item no: ', 5, ' ', 'Banana')

popping items from the queue:

('item no: ', 0, ' ', 'Apple')

('item no: ', 1, ' ', 'Orange')

('item no: ', 2, ' ', 'Guava')

('item no: ', 3, ' ', 'Papaya')

('item no: ', 4, ' ', 'Banana')

Explanation -

In the preceding code, the Queue class has been imported, and a list called fruits has been initialized. Following that, we set a variable named count to 1, which will keep track of the total number of items. Subsequently, we instantiated the queue object by invoking the Queue method. This object will facilitate operations within the Queue. Within a for loop, we added elements to the queue individually using the put function, incrementing the count by 1 with each iteration of the loop.

Python Multiprocessing Lock Class

The Lock class in multiprocessing is utilized to gain a lock on a process, preventing other processes from executing similar code until the lock is freed. This class primarily serves two functions: the first is to obtain a lock via the acquire method, and the second is to relinquish the lock using the release method.

Python Multiprocessing Lock Class Example

Let’s consider a scenario involving several tasks. We will set up two separate queues: one will hold the tasks, while the second will keep a log of completed tasks. The subsequent step involves initializing the processes required to execute the tasks. As mentioned earlier, the Queue class is inherently synchronized, which means there is no requirement to obtain a lock using the Lock class.

In the subsequent illustration, we will consolidate all the multiprocessing classes. Please refer to the example below.

Example

from multiprocessing import Lock, Process, Queue, current_process

import time

import queue

def jobTodo(tasks_to_perform, complete_tasks):

    while True:

        try:

            # The try block to catch task from the queue.

            # The get_nowait() function is used to

            # raise queue.Empty exception if the queue is empty.

            task = tasks_to_perform.get_nowait()

        except queue.Empty:

            break

        else:

                # if no exception has been raised, the else block will execute

                # add the task completion

            print(task)

            complete_tasks.put(task + ' is done by ' + current_process().name)

            time.sleep(.5)

    return True

def main():

    total_task = 8

    total_number_of_processes = 3

    tasks_to_perform = Queue()

    complete_tasks = Queue()

    number_of_processes = []

    for i in range(total_task):

        tasks_to_perform.put("Task no " + str(i))

    # defining number of processes

    for w in range(total_number_of_processes):

        p = Process(target=jobTodo, args=(tasks_to_perform, complete_tasks))

        number_of_processes.append(p)

        p.start()

    # completing process

    for p in number_of_processes:

        p.join()

    # print the output

    while not complete_tasks.empty():

        print(complete_tasks.get())

    return True

if __name__ == '__main__':

    main()

Output:

Output

Task no 2

Task no 5

Task no 0

Task no 3

Task no 6

Task no 1

Task no 4

Task no 7

Task no 0 is done by Process-1

Task no 1 is done by Process-3

Task no 2 is done by Process-2

Task no 3 is done by Process-1

Task no 4 is done by Process-3

Task no 5 is done by Process-2

Task no 6 is done by Process-1

Task no 7 is done by Process-3

Python Multiprocessing Pool

The Python multiprocessing pool is crucial for executing a function concurrently over various input values. Additionally, it facilitates the allocation of input data among different processes (data parallelism).

Python Multiprocessing Pool Example

Examine the subsequent illustration of a multiprocessing Pool.

Example

from multiprocessing import Pool

import time

w = (["V", 5], ["X", 2], ["Y", 1], ["Z", 3])

def work_log(data_for_work):

    print(" Process name is %s waiting time is %s seconds" % (data_for_work[0], data_for_work[1]))

    time.sleep(int(data_for_work[1]))

    print(" Process %s Executed." % data_for_work[0])

def handler():

    p = Pool(2)

    p.map(work_log, w)

if __name__ == '__main__':

    handler()

Output:

Output

Process name is V waiting time is 5 seconds

Process V Executed.

Process name is X waiting time is 2 seconds

Process X Executed.

Process name is Y waiting time is 1 seconds

Process Y Executed.

Process name is Z waiting time is 3 seconds

Process Z Executed.

Another Python Example for Multiprocessing Pool

Let us examine an additional instance of the multiprocessing Pool.

Example

from multiprocessing import Pool

def fun(x):

    return x*x

if __name__ == '__main__':

    with Pool(5) as p:

        print(p.map(fun, [1, 2, 3]))

Output:

Output

[1, 8, 27]

Proxy Objects

The objects known as proxies are categorized as shared objects that exist within a separate process. This entity is also identified as a proxy. It is possible for multiple proxy objects to reference the same referent. A proxy object includes several methods designed to call the corresponding methods of its referent.

Python Proxy Objects Example

Let's consider an example to illustrate the use of proxy objects in Python.

Example

from multiprocessing import Manager

manager = Manager()

l = manager.list([i*i for i in range(10)])

print(l)

print(repr(l))

print(l[4])

print(l[2:5])

Output:

Output

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

<ListProxy object, typeid 'list' at 0x7f063621ea10>

16

[4, 9, 16]

The proxy objects are capable of being pickled, allowing for their transfer between processes. Additionally, these objects facilitate a degree of control regarding synchronization.

Commonly Used Functions of Multiprocessing

So far, we have discussed the basic concepts of multiprocessing using Python. Multiprocessing is a broad topic itself and essential for performing various tasks within a single system. We are defining a few essential functions that are commonly used to achieve multiprocessing.

Method Description
pipe() The pipe() function returns a pair of connection objects.
run() The run() method is used to represent the process activities.
start() The start()method is used to start the process.
join([timeout]) The join() method is used to block the process until the process whose join() method is called terminates. The timeout is optional argument.
is_alive() It returns if process is alive.
terminate() As the name suggests, it is used to terminate the process. Always remember - theterminate()method is used in Linux, for Windows, we useTerminateProcess()method.
kill() This method is similar to theterminate()but using the SIGKILL signal on Unix.
close() This method is used to close theProcessobject and releases all resources associated with it.
qsize() It returns the approximate size of the queue.
empty() If queue is empty, it returnsTrue.
full() It returnsTrue, if queue is full.
get_await() This method is equivalentget(False).
get() This method is used to get elements from the queue. It removes and returns an element from queue.
put() This method is used to insert an element into the queue.
cpu_count() It returns the number of working CPU within the system.
current_process() It returns the Process object corresponding to the current process.
parent_process() It returns the parent Process object corresponding to the current process.
task_done() This function is used indicate that an enqueued task is completed.
join_thread() This method is used to join the background thread

Input Required

This code uses input(). Please provide values below: