03_concurrency_parallelism.py
Python/03_advanced_level/03_concurrency_parallelism.py
# Concurrency & Parallelism Examples
import threading
import time
import requests
import multiprocessing
import os
import asyncio
import aiohttp
import queue
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
import math
import random
print("Concurrency & Parallelism Examples")
print("=" * 40)
# Understanding Concurrency vs Parallelism
print("1. Understanding Concurrency vs Parallelism")
print("-" * 45)
def simple_task(name, duration):
"""Simple task that simulates work."""
print(f"Task {name} starting")
time.sleep(duration)
print(f"Task {name} completed")
# Sequential execution
print("Sequential Execution:")
start_time = time.time()
simple_task("A", 1)
simple_task("B", 1)
simple_task("C", 1)
sequential_time = time.time() - start_time
print(f"Sequential execution took: {sequential_time:.2f} seconds")
# Threaded execution
print("\nThreaded Execution:")
start_time = time.time()
threads = []
for i in range(3):
thread = threading.Thread(target=simple_task, args=(f"Thread-{i}", 1))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
threaded_time = time.time() - start_time
print(f"Threaded execution took: {threaded_time:.2f} seconds")
# Thread-safe counter example
print("\n2. Thread-Safe Counter")
print("-" * 25)
class ThreadSafeCounter:
"""Thread-safe counter using locks."""
def __init__(self):
self._value = 0
self._lock = threading.Lock()
def increment(self):
"""Increment counter safely."""
with self._lock:
self._value += 1
def get_value(self):
"""Get current counter value."""
with self._lock:
return self._value
def increment_by(self, amount):
"""Increment counter by specified amount."""
with self._lock:
self._value += amount
def worker(counter, iterations):
"""Worker function that increments counter."""
for _ in range(iterations):
counter.increment()
# Thread-safe counter demo
counter = ThreadSafeCounter()
threads = []
# Create multiple threads that increment the counter
for i in range(5):
thread = threading.Thread(target=worker, args=(counter, 1000))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
print(f"Final counter value: {counter.get_value()}")
print(f"Expected value: {5 * 1000}")
# Producer-Consumer Pattern
print("\n3. Producer-Consumer Pattern")
print("-" * 30)
class ProducerConsumer:
"""Producer-consumer pattern using threads and queues."""
def __init__(self, max_size=5):
self.queue = queue.Queue(maxsize=max_size)
self.results = []
self.lock = threading.Lock()
def producer(self, items):
"""Produce items and put them in queue."""
for item in items:
print(f"Producing: {item}")
self.queue.put(item)
time.sleep(0.1) # Simulate work
print("Producer finished")
def consumer(self, consumer_id):
"""Consume items from queue."""
while True:
try:
item = self.queue.get(timeout=1)
print(f"Consumer {consumer_id} processing: {item}")
time.sleep(0.2) # Simulate work
with self.lock:
self.results.append(f"Processed by {consumer_id}: {item}")
self.queue.task_done()
except queue.Empty:
print(f"Consumer {consumer_id} finished")
break
# Using producer-consumer
pc = ProducerConsumer()
# Create producer thread
producer_thread = threading.Thread(
target=pc.producer,
args=([f"Item-{i}" for i in range(10)],)
)
# Create consumer threads
consumer_threads = []
for i in range(3):
consumer_thread = threading.Thread(
target=pc.consumer,
args=(i,)
)
consumer_threads.append(consumer_thread)
# Start all threads
producer_thread.start()
for thread in consumer_threads:
thread.start()
# Wait for completion
producer_thread.join()
for thread in consumer_threads:
thread.join()
print(f"Results: {pc.results}")
# ThreadPoolExecutor
print("\n4. ThreadPoolExecutor")
print("-" * 22)
def fetch_url(url):
"""Fetch URL content."""
try:
response = requests.get(url, timeout=5)
return {
'url': url,
'status': response.status_code,
'length': len(response.content)
}
except Exception as e:
return {
'url': url,
'status': 'error',
'error': str(e)
}
# ThreadPoolExecutor demo
urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/2',
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/3',
'https://httpbin.org/delay/1'
]
start_time = time.time()
with ThreadPoolExecutor(max_workers=3) as executor:
# Submit all tasks
future_to_url = {executor.submit(fetch_url, url): url for url in urls}
# Process completed tasks
for future in as_completed(future_to_url):
url = future_to_url[future]
try:
result = future.result()
print(f"URL: {result['url']}, Status: {result['status']}")
except Exception as e:
print(f"URL: {url}, Error: {e}")
executor_time = time.time() - start_time
print(f"ThreadPoolExecutor took: {executor_time:.2f} seconds")
# Multiprocessing
print("\n5. Multiprocessing")
print("-" * 18)
def cpu_intensive_task(n):
"""CPU-intensive task."""
print(f"Process {os.getpid()} processing {n}")
# Simulate CPU-intensive work
result = sum(i * i for i in range(n))
return result
# Sequential execution
start_time = time.time()
sequential_results = [cpu_intensive_task(100000) for _ in range(4)]
sequential_time = time.time() - start_time
print(f"Sequential execution took: {sequential_time:.2f} seconds")
# Multiprocessing execution
start_time = time.time()
with multiprocessing.Pool(processes=4) as pool:
multiprocessing_results = pool.map(cpu_intensive_task, [100000] * 4)
multiprocessing_time = time.time() - start_time
print(f"Multiprocessing execution took: {multiprocessing_time:.2f} seconds")
print(f"Speedup: {sequential_time / multiprocessing_time:.2f}x")
# Process communication with Queue
print("\n6. Process Communication")
print("-" * 25)
def worker_process(name, queue, results):
"""Worker process that processes items from queue."""
while True:
try:
item = queue.get(timeout=1)
print(f"Process {name} (PID: {os.getpid()}) processing: {item}")
# Simulate work
time.sleep(0.5)
result = item * 2
results.append(result)
queue.task_done()
except:
print(f"Process {name} finished")
break
manager = multiprocessing.Manager()
queue = manager.Queue()
results = manager.list()
# Add items to queue
for i in range(10):
queue.put(i)
# Create and start processes
processes = []
for i in range(3):
process = multiprocessing.Process(
target=worker_process,
args=(f"Worker-{i}", queue, results)
)
processes.append(process)
process.start()
# Wait for all processes to complete
for process in processes:
process.join()
print(f"Results: {list(results)}")
# Shared memory
def modify_shared_array(shared_array, start, end):
"""Modify a portion of shared array."""
for i in range(start, end):
shared_array[i] = shared_array[i] * 2
# Shared memory demo
print("\n7. Shared Memory")
print("-" * 15)
shared_array = multiprocessing.Array('i', range(10))
print(f"Original array: {list(shared_array)}")
# Create processes to modify different portions
processes = []
chunk_size = len(shared_array) // 2
for i in range(2):
start = i * chunk_size
end = start + chunk_size
process = multiprocessing.Process(
target=modify_shared_array,
args=(shared_array, start, end)
)
processes.append(process)
process.start()
# Wait for processes to complete
for process in processes:
process.join()
print(f"Modified array: {list(shared_array)}")
# ProcessPoolExecutor
print("\n8. ProcessPoolExecutor")
print("-" * 22)
def is_prime(n):
"""Check if a number is prime."""
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
def find_primes_in_range(start, end):
"""Find all primes in a range."""
primes = []
for num in range(start, end):
if is_prime(num):
primes.append(num)
return primes
# Sequential prime finding
start_time = time.time()
sequential_primes = find_primes_in_range(2, 10000)
sequential_time = time.time() - start_time
print(f"Sequential found {len(sequential_primes)} primes in {sequential_time:.2f} seconds")
# Parallel prime finding
start_time = time.time()
# Divide work into chunks
chunk_size = 2500
ranges = [(i, i + chunk_size) for i in range(2, 10000, chunk_size)]
with ProcessPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(find_primes_in_range, start, end)
for start, end in ranges]
parallel_primes = []
for future in as_completed(futures):
primes = future.result()
parallel_primes.extend(primes)
parallel_time = time.time() - start_time
print(f"Parallel found {len(parallel_primes)} primes in {parallel_time:.2f} seconds")
print(f"Speedup: {sequential_time / parallel_time:.2f}x")
# Asyncio
print("\n9. Asyncio")
print("-" * 12)
async def simple_async_task(name, duration):
"""Simple async task."""
print(f"Task {name} starting")
await asyncio.sleep(duration)
print(f"Task {name} completed")
return f"Result from {name}"
async def fetch_url_async(session, url):
"""Fetch URL content asynchronously."""
try:
async with session.get(url) as response:
content = await response.text()
return {
'url': url,
'status': response.status,
'length': len(content)
}
except Exception as e:
return {
'url': url,
'status': 'error',
'error': str(e)
}
# Basic asyncio demo
async def basic_asyncio_demo():
"""Demonstrate basic asyncio functionality."""
print("Basic Asyncio Demo:")
start_time = time.time()
# Run tasks concurrently
tasks = [
simple_async_task("A", 1),
simple_async_task("B", 1),
simple_async_task("C", 1)
]
results = await asyncio.gather(*tasks)
asyncio_time = time.time() - start_time
print(f"Async execution took: {asyncio_time:.2f} seconds")
print(f"Results: {results}")
# Run the basic demo
asyncio.run(basic_asyncio_demo())
# Async context manager
class AsyncDatabaseConnection:
"""Async database connection context manager."""
async def __aenter__(self):
print("Connecting to database...")
await asyncio.sleep(0.1) # Simulate connection time
self.connected = True
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Closing database connection...")
await asyncio.sleep(0.1) # Simulate disconnection time
self.connected = False
async def query(self, sql):
"""Execute a query."""
if not self.connected:
raise RuntimeError("Not connected to database")
await asyncio.sleep(0.1) # Simulate query time
return f"Result of: {sql}"
# Async context manager demo
async def async_context_demo():
"""Demonstrate async context manager."""
print("\nAsync Context Manager Demo:")
async with AsyncDatabaseConnection() as db:
result1 = await db.query("SELECT * FROM users")
print(result1)
result2 = await db.query("INSERT INTO users VALUES (1, 'John')")
print(result2)
asyncio.run(async_context_demo())
# Async producer-consumer
class AsyncProducerConsumer:
"""Async producer-consumer using asyncio."""
def __init__(self, max_size=5):
self.queue = asyncio.Queue(maxsize=max_size)
self.results = []
async def producer(self, items):
"""Produce items and put them in queue."""
for item in items:
print(f"Producing: {item}")
await self.queue.put(item)
await asyncio.sleep(0.1) # Simulate work
print("Producer finished")
async def consumer(self, consumer_id):
"""Consume items from queue."""
while True:
try:
item = await asyncio.wait_for(self.queue.get(), timeout=1.0)
print(f"Consumer {consumer_id} processing: {item}")
await asyncio.sleep(0.2) # Simulate work
self.results.append(f"Processed by {consumer_id}: {item}")
self.queue.task_done()
except asyncio.TimeoutError:
print(f"Consumer {consumer_id} finished")
break
# Async producer-consumer demo
async def async_producer_consumer_demo():
"""Demonstrate async producer-consumer."""
print("\nAsync Producer-Consumer Demo:")
pc = AsyncProducerConsumer()
# Create tasks
producer_task = asyncio.create_task(
pc.producer([f"Item-{i}" for i in range(10)])
)
consumer_tasks = [
asyncio.create_task(pc.consumer(i))
for i in range(3)
]
# Wait for producer to finish
await producer_task
# Wait for consumers to finish
await asyncio.gather(*consumer_tasks)
print(f"Results: {pc.results}")
asyncio.run(async_producer_consumer_demo())
# Async semaphore for rate limiting
class AsyncRateLimiter:
"""Async rate limiter using semaphore."""
def __init__(self, rate_limit):
self.semaphore = asyncio.Semaphore(rate_limit)
async def acquire(self):
"""Acquire a permit."""
await self.semaphore.acquire()
def release(self):
"""Release a permit."""
self.semaphore.release()
async def __aenter__(self):
await self.acquire()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
self.release()
async def rate_limited_task(task_id, rate_limiter):
"""Task that respects rate limiting."""
async with rate_limiter:
print(f"Task {task_id} starting")
await asyncio.sleep(random.uniform(0.5, 1.5))
print(f"Task {task_id} completed")
# Rate limiting demo
async def rate_limiting_demo():
"""Demonstrate rate limiting."""
print("\nRate Limiting Demo:")
rate_limiter = AsyncRateLimiter(2) # Allow 2 concurrent tasks
tasks = [
asyncio.create_task(rate_limited_task(i, rate_limiter))
for i in range(10)
]
await asyncio.gather(*tasks)
asyncio.run(rate_limiting_demo())
# Performance comparison
print("\n10. Performance Comparison")
print("-" * 28)
def sync_task(url):
"""Synchronous task."""
try:
response = requests.get(url, timeout=5)
return {'url': url, 'status': response.status_code}
except:
return {'url': url, 'status': 'error'}
def threading_task(urls):
"""Threading implementation."""
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(sync_task, urls))
return results
def multiprocessing_task(urls):
"""Multiprocessing implementation."""
with ProcessPoolExecutor(max_workers=5) as executor:
results = list(executor.map(sync_task, urls))
return results
async def async_task(session, url):
"""Async task."""
try:
async with session.get(url) as response:
return {'url': url, 'status': response.status}
except:
return {'url': url, 'status': 'error'}
async def asyncio_task(urls):
"""Asyncio implementation."""
async with aiohttp.ClientSession() as session:
tasks = [async_task(session, url) for url in urls]
results = await asyncio.gather(*tasks)
return results
# Performance comparison
async def performance_comparison():
"""Compare different concurrency approaches."""
print("Performance Comparison:")
urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/1'
]
# Synchronous
start_time = time.time()
sync_results = [sync_task(url) for url in urls]
sync_time = time.time() - start_time
print(f"Synchronous: {sync_time:.2f} seconds")
# Threading
start_time = time.time()
threading_results = threading_task(urls)
threading_time = time.time() - start_time
print(f"Threading: {threading_time:.2f} seconds")
# Multiprocessing
start_time = time.time()
multiprocessing_results = multiprocessing_task(urls)
multiprocessing_time = time.time() - start_time
print(f"Multiprocessing: {multiprocessing_time:.2f} seconds")
# Asyncio
start_time = time.time()
asyncio_results = await asyncio_task(urls)
asyncio_time = time.time() - start_time
print(f"Asyncio: {asyncio_time:.2f} seconds")
# Speedup comparison
print(f"\nSpeedup vs Synchronous:")
print(f"Threading: {sync_time / threading_time:.2f}x")
print(f"Multiprocessing: {sync_time / multiprocessing_time:.2f}x")
print(f"Asyncio: {sync_time / asyncio_time:.2f}x")
# Run performance comparison
asyncio.run(performance_comparison())
print("\nConcurrency & Parallelism examples completed!")
관련 글
01_getting_started.py
01_getting_started.py — python source code from the Python learning materials (Python/01_beginner_level/01_getting_started.py).
글 읽기 →02_core_syntax.py
02_core_syntax.py — python source code from the Python learning materials (Python/01_beginner_level/02_core_syntax.py).
글 읽기 →03_control_flow.py
03_control_flow.py — python source code from the Python learning materials (Python/01_beginner_level/03_control_flow.py).
글 읽기 →04_data_structures.py
04_data_structures.py — python source code from the Python learning materials (Python/01_beginner_level/04_data_structures.py).
글 읽기 →05_functions.py
05_functions.py — python source code from the Python learning materials (Python/01_beginner_level/05_functions.py).
글 읽기 →06_error_handling.py
06_error_handling.py — python source code from the Python learning materials (Python/01_beginner_level/06_error_handling.py).
글 읽기 →