#!/usr/bin/env python3 # Imports import threading import multiprocessing import concurrent.futures import time def task(name): """ Dummy function which pretends to do some work """ print(f"Thread {name}: Starting...") time.sleep(1) print(f"Thread {name}: Finishing.") def threading_example(): """ threading: not truly concurrent as GIL (Global Interpreter Lock) limits 1 process for each bytecode execution. it does allow the process to do more work while other threads are not busy. threading is relatively light-weight """ threads = [] for i in range(3): threads.append(threading.Thread(target=task, args=(i,))) for i in threads: i.start() for i in threads: i.join() print("threading_example: All threads completed.") def multiprocessing_example(): """ multiprocessing: True parallel execution on multiple CPU cores. Tasks are ran on independent processes. More resource expensive compared to threading """ mp = [] for i in range(3): mp.append(multiprocessing.Process(target=task, args=(i,))) for i in mp: i.start() for i in mp: i.join() print("multiprocessing_example: Done with all calculations!") def concurrent_futures_example(): """ high-level implementation of threading. facilitate result consolidation for high-level implemetnation of multiprocessing, use ProcessPoolExecutor """ with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: for i in range(3): executor.submit(task, i) print("concurrent_futures_example: All threads completed.") # Main function def main() -> None: threading_example() multiprocessing_example() concurrent_futures_example() # Call main function if __name__ == '__main__': main()