parallel programming
concurrent execution
multithreading
asynchronous functions
parallel computing

How to run functions in parallel?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Running functions in parallel can significantly enhance the performance of your applications, particularly when dealing with CPU-bound or I/O-bound operations. By executing multiple functions simultaneously, you can reduce execution time and make efficient use of system resources. This article provides a comprehensive guide on how to run functions in parallel, exploring techniques from threading and multiprocessing to more advanced methods like concurrent futures and asynchronous programming.

Understanding Parallelism

Parallelism refers to performing multiple tasks or computations simultaneously. It is distinct from concurrency, which is more about managing multiple tasks that may not necessarily execute simultaneously. Here are the primary methods for achieving parallelism in Python:

  1. Threading: Useful for I/O-bound tasks, it allows multiple threads to run within the same process.
  2. Multiprocessing: Suitable for CPU-bound tasks, it runs tasks in separate processes, utilizing multiple CPUs.
  3. Concurrent Futures: A higher-level interface for managing asynchronous execution of tasks.
  4. Async/Await: Implements concurrent code using asynchronous programming, mostly for I/O-bound operations.

Threading

Threading allows multiple threads to operate in the same process space, making it less memory-intensive. However, due to the Global Interpreter Lock (GIL) in Python, true parallel execution is limited when working with CPU-bound tasks.

python
1import threading
2
3def print_numbers():
4    for i in range(5):
5        print(i)
6
7# Creating threads
8thread1 = threading.Thread(target=print_numbers)
9thread2 = threading.Thread(target=print_numbers)
10
11# Starting threads
12thread1.start()
13thread2.start()
14
15# Joining threads to the main thread
16thread1.join()
17thread2.join()

Multiprocessing

The multiprocessing module overcomes the limitations of threading by using separate memory space for each process, bypassing the GIL. It's more suitable for CPU-bound tasks.

python
1from multiprocessing import Process
2
3def print_numbers():
4    for i in range(5):
5        print(i)
6
7# Creating processes
8process1 = Process(target=print_numbers)
9process2 = Process(target=print_numbers)
10
11# Starting processes
12process1.start()
13process2.start()
14
15# Joining processes to the main process
16process1.join()
17process2.join()

Concurrent Futures

The concurrent.futures module provides a high-level API for asynchronously executing functions. It supports thread and process pools for easy management of concurrency.

python
1from concurrent.futures import ThreadPoolExecutor, as_completed
2
3def task(n):
4    return n * n
5
6with ThreadPoolExecutor(max_workers=2) as executor:
7    futures = [executor.submit(task, i) for i in range(5)]
8    for future in as_completed(futures):
9        print(future.result())

Async/Await

Asynchronous programming is ideal for I/O-bound tasks and is facilitated by the asyncio library. It allows for code execution without waiting for blocking operations.

python
1import asyncio
2
3async def async_task(n):
4    await asyncio.sleep(1)
5    print(f'Task {n} complete')
6
7async def main():
8    tasks = [async_task(i) for i in range(5)]
9    await asyncio.gather(*tasks)
10
11asyncio.run(main())

Performance Considerations

  • CPU-Bound: Use multiprocessing to leverage multiple CPU cores and bypass the GIL.
  • I/O-Bound: Threading or async/await are preferred as they allow overlapping execution during wait times.

Key Points Summary

ConceptIdeal Use CaseRestrictionsExample Module
ThreadingI/O-Bound OperationsGIL limits parallelismthreading
MultiprocessingCPU-Bound OperationsOverhead per processmultiprocessing
Concurrent FuturesGeneral PurposeExecutor Managementconcurrent.futures
Async/AwaitI/O-Bound OperationsPython 3.5+asyncio

Conclusion

Understanding how to execute functions in parallel is crucial for optimizing application performance. Choosing the right technique depends on the specific requirements of your tasks, whether they are CPU-bound or I/O-bound. By employing threading, multiprocessing, or asynchronous programming, you can effectively reduce execution time and improve resource utilization.


Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.