Understanding Multiprocessing, Multithreading, and Coroutines in Python
Multiprocessing Fundamentals
A process represents an executing application within the operating system, serving as an independent entity for resource allocation with its own memory space. While a single process can contain multiple threads sharing the process's stack, multiprocessing enables true parallelism by utilizing separate processes.
Example implementation using multiprocessing:
import random
import time
from multiprocessing import Process
def fetch_data(file_name):
print(f'Downloading {file_name}...')
duration = random.randint(5, 10)
time.sleep(duration)
print(f'{file_name} downloaded in {duration} seconds')
def execute_parallel_tasks():
initial_time = time.time()
process_one = Process(target=fetch_data, args=("Advanced_Python_Guide.pdf",))
process_two = Process(target=fetch_data, args=("Demo_Video.mp4",))
process_one.start()
process_two.start()
process_one.join()
process_two.join()
final_time = time.time()
print(f'Total execution time: {final_time - initial_time:.2f} seconds')
if __name__ == '__main__':
execute_parallel_tasks()
Key multiprocessing components:
Process: Creates process instancestarget: Specifies the function to executeargs: Tuple containing function argumentsstart(): Initiates process executionjoin(): Blocks until process completion
Multithreading Concepts
Multithreading enables concurrent execution of multiple threads within a single proces, leveraging hardware capabilities for improved performance.
Benefits include:
- Background processing of time-intensive operations
- Enhanced system responsiveness on multi-core architectures
Thread-based implementation example:
import random
import time
from threading import Thread
def retrieve_content(document_name):
print(f'Retrieving {document_name}...')
wait_period = random.randint(5, 10)
time.sleep(wait_period)
print(f'{document_name} retrieved in {wait_period} seconds')
def run_concurrent_threads():
begin_time = time.time()
thread_a = Thread(target=retrieve_content, args=("Python_Development_Handbook.pdf",))
thread_b = Thread(target=retrieve_content, args=("Sample_Movie.avi",))
thread_a.start()
thread_b.start()
thread_a.join()
thread_b.join()
finish_time = time.time()
print(f'Overall duration: {finish_time - begin_time:.2f} seconds')
if __name__ == '__main__':
run_concurrent_threads()
Coroutine Implementation
Coroutines provide lightweight concurrency mechanisms that optimize resource usage compared to traditional threading. They enable cooperative multitasking where execution switches only when tasks encounter blocking operations.
Core asyncio concepts:
- Event loop: Continuously monitors and executes coroutine functions
- Coroutine object: Created using
asynckeyword, representing suspended execution - Task: Enhanced coroutine wrapper for better management
- Future: Represents eventual result of asynchronous operation
async/await: Keywords for defining and managing coroutines
Coroutine demonstration:
import asyncio
import random
import time
async def handle_download(resource_name):
print(f'Starting download of {resource_name}...')
processing_time = random.randint(5, 10)
await asyncio.sleep(processing_time)
print(f'{resource_name} downloaded in {processing_time} seconds')
async def orchestrate_downloads():
start_timestamp = time.time()
task_one = handle_download("Comprehensive_Python_Manual.pdf")
task_two = handle_download("Presentation_Video.mp4")
await asyncio.gather(task_one, task_two)
end_timestamp = time.time()
print(f'Combined execution time: {end_timestamp - start_timestamp:.2f} seconds')
if __name__ == '__main__':
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(orchestrate_downloads())
finally:
event_loop.close()
Essential coroutine workflow:
- Define functions with
asynckeyword - Create coroutine objects through function calls
- Obtain event loop via
asyncio.get_event_loop() - Execute coroutines using
run_until_complete()