KR_Threading - somaz94/python-study GitHub Wiki
์ค๋ ๋๋ ํ๋ก๊ทธ๋จ ๋ด์์ ๋์์ ์คํ๋๋ ์์ ์คํ ๋จ์๋ก, ๊ฐ์ ํ๋ก์ธ์ค ๋ด์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ณต์ ํฉ๋๋ค.
import threading
import time
from typing import List, Dict, Any, Optional
def worker(name: str, sleep_time: float = 2.0) -> None:
"""
๊ธฐ๋ณธ ์์
์ ์ค๋ ๋ ํจ์
Args:
name: ์์
์๋ณ ์ด๋ฆ
sleep_time: ๋๊ธฐ ์๊ฐ(์ด)
"""
print(f"์ค๋ ๋ {name} ์์")
time.sleep(sleep_time)
print(f"์ค๋ ๋ {name} ์ข
๋ฃ")
# ์ค๋ ๋ ์์ฑ ๋ฐ ์์
thread = threading.Thread(target=worker, args=("Worker-1",))
thread.daemon = True # ๋ฉ์ธ ์ค๋ ๋ ์ข
๋ฃ์ ํจ๊ป ์ข
๋ฃ
thread.start()
# ์ค๋ ๋ ์ ๋ณด ํ์ธ
print(f"์ค๋ ๋ ID: {thread.ident}")
print(f"์ค๋ ๋ ์ด๋ฆ: {thread.name}")
print(f"์ค๋ ๋ ํ์ฑ ์ํ: {thread.is_alive()}")
thread.join() # ์ค๋ ๋๊ฐ ์ข
๋ฃ๋ ๋๊น์ง ๋๊ธฐ
โ ํน์ง:
- ์ค๋ ๋ ์์ฑ๊ณผ ์คํ
- ๋ฐ๋ชฌ ์ค๋ ๋ ์ค์ ๊ฐ๋ฅ
- ์ค๋ ๋ ID์ ์ํ ํ์ธ
- join()์ผ๋ก ์ค๋ ๋ ์๋ฃ ๋๊ธฐ
- ํ์ ํํ ์ผ๋ก ์ฝ๋ ๊ฐ๋ ์ฑ ํฅ์
์ฌ๋ฌ ์ค๋ ๋๋ฅผ ๋์์ ์คํํ์ฌ ๋ณ๋ ฌ ์์ ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
import threading
import time
from typing import List, Iterable, Any
def count_numbers(name: str, numbers: Iterable[int]) -> None:
"""
์ฃผ์ด์ง ์ซ์ ๋ชฉ๋ก์ ์ํํ๋ฉด์ ์ถ๋ ฅํ๋ ์ค๋ ๋ ํจ์
Args:
name: ์ค๋ ๋ ์ด๋ฆ
numbers: ์ฒ๋ฆฌํ ์ซ์ ๋ชฉ๋ก
"""
for i in numbers:
print(f"{name}: {i}")
time.sleep(0.1)
class WorkerThread(threading.Thread):
"""
Thread ํด๋์ค๋ฅผ ์์๋ฐ๋ ์ฌ์ฉ์ ์ ์ ์ค๋ ๋ ํด๋์ค
"""
def __init__(self, name: str, numbers: Iterable[int]) -> None:
"""
์์ฑ์
Args:
name: ์ค๋ ๋ ์ด๋ฆ
numbers: ์ฒ๋ฆฌํ ์ซ์ ๋ชฉ๋ก
"""
super().__init__(name=name)
self.numbers = numbers
self.result: List[int] = []
def run(self) -> None:
"""์ค๋ ๋ ์คํ ์ ํธ์ถ๋๋ ๋ฉ์๋"""
print(f"{self.name} ์์")
for num in self.numbers:
self.result.append(num * 2) # ๊ฐ ์ซ์๋ฅผ 2๋ฐฐ๋ก ์ฒ๋ฆฌ
time.sleep(0.1)
print(f"{self.name} ์ข
๋ฃ")
def get_result(self) -> List[int]:
"""์ฒ๋ฆฌ ๊ฒฐ๊ณผ ๋ฐํ"""
return self.result
# ํจ์ ๊ธฐ๋ฐ ๋ค์ค ์ค๋ ๋ ์คํ
threads: List[threading.Thread] = []
for i in range(3):
thread = threading.Thread(
target=count_numbers,
args=(f"Thread-{i}", range(i*3, (i+1)*3))
)
threads.append(thread)
thread.start()
# ํด๋์ค ๊ธฐ๋ฐ ๋ค์ค ์ค๋ ๋ ์คํ
worker_threads: List[WorkerThread] = []
for i in range(3):
worker = WorkerThread(f"WorkerThread-{i}", range(i*3, (i+1)*3))
worker_threads.append(worker)
worker.start()
# ๋ชจ๋ ์ค๋ ๋ ์ข
๋ฃ ๋๊ธฐ
for thread in threads:
thread.join()
# ์์
๊ฒฐ๊ณผ ์์ง
results: List[List[int]] = []
for worker in worker_threads:
worker.join()
results.append(worker.get_result())
print(f"์ฒ๋ฆฌ ๊ฒฐ๊ณผ: {results}")
โ ํน์ง:
- ํจ์ ๊ธฐ๋ฐ ๋ฐ ํด๋์ค ๊ธฐ๋ฐ ์ค๋ ๋ ๊ตฌํ
- Thread ํด๋์ค ์์์ ํตํ ํ์ฅ
- ๋ค์ค ์ค๋ ๋ ์์ฑ ๋ฐ ๊ด๋ฆฌ
- ๋งค๊ฐ๋ณ์ ์ ๋ฌ๊ณผ ๊ฒฐ๊ณผ ์์ง
- ์ค๋ ๋ ๊ฐ ๋ ๋ฆฝ์ ์คํ
์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ณต์ ์์์ ์ ๊ทผํ ๋ ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ์ ์งํ๊ธฐ ์ํ ๋๊ธฐํ ๋ฉ์ปค๋์ฆ์ ๋๋ค.
import threading
import time
from typing import List, Dict
class Counter:
"""์ค๋ ๋ ์์ ํ ์นด์ดํฐ ํด๋์ค"""
def __init__(self) -> None:
self.value = 0
self.lock = threading.Lock()
def increment(self) -> None:
"""๋ฝ์ ์ฌ์ฉํ ์์ ํ ์ฆ๊ฐ ์ฐ์ฐ"""
with self.lock:
current = self.value
time.sleep(0.1) # ๊ฒฝ์ ์กฐ๊ฑด ์๋ฎฌ๋ ์ด์
self.value = current + 1
def safe_update(self, func) -> None:
"""์ฌ์ฉ์ ์ ์ ์
๋ฐ์ดํธ ํจ์๋ฅผ ๋ฝ์ผ๋ก ๋ณดํธ"""
with self.lock:
func(self)
# RLock ์ฌ์ฉ ์์ (์ฌ์ง์
๊ฐ๋ฅ ๋ฝ)
class RecursiveCounter:
"""์ฌ์ง์
๋ฝ์ ์ฌ์ฉํ๋ ์นด์ดํฐ"""
def __init__(self) -> None:
self.value = 0
self.lock = threading.RLock() # ์ฌ์ง์
๊ฐ๋ฅํ ๋ฝ
def increment(self, n: int = 1) -> None:
"""๊ฐ ์ฆ๊ฐ"""
with self.lock:
self.value += n
def increment_by_10(self) -> None:
"""10์ฉ ์ฆ๊ฐ (๋ด๋ถ์ ์ผ๋ก increment ํธ์ถ)"""
with self.lock: # ๊ฐ์ ์ค๋ ๋์์ ์ด๋ฏธ ํ๋ํ ๋ฝ ์ฌ์ง์
for _ in range(10):
self.increment() # ๋ฝ์ ์ด๋ฏธ ๋ณด์ ํ ์ํ์์ ํธ์ถ
# ๊ธฐ๋ณธ ๋ฝ ์ฌ์ฉ ์์
counter = Counter()
def increment_task() -> None:
"""์นด์ดํฐ ์ฆ๊ฐ ์์
"""
for _ in range(3):
counter.increment()
threads: List[threading.Thread] = [
threading.Thread(target=increment_task) for _ in range(5)
]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print(f"์ต์ข
์นด์ดํฐ ๊ฐ: {counter.value}") # ์์ ๊ฒฐ๊ณผ: 15
# ์ฌ์ง์
๋ฝ ์ฌ์ฉ ์์
recursive_counter = RecursiveCounter()
recursive_counter.increment_by_10()
print(f"์ฌ์ง์
๋ฝ ์นด์ดํฐ ๊ฐ: {recursive_counter.value}") # ์์ ๊ฒฐ๊ณผ: 10
โ ํน์ง:
- Lock์ ์ฌ์ฉํ ์ํธ ๋ฐฐ์
- RLock์ ํตํ ์ฌ์ง์ ์ง์
- ์ปจํ ์คํธ ๊ด๋ฆฌ์(with ๋ฌธ) ํ์ฉ
- ์ค๋ ๋ ์์ ํ ํด๋์ค ๊ตฌํ
- ๊ฒฝ์ ์กฐ๊ฑด(Race Condition) ๋ฐฉ์ง
์ค๋ ๋ ๊ฐ ํต์ ๊ณผ ์ํ ์๋ฆผ์ ์ํ ๋ฉ์ปค๋์ฆ์ ๋๋ค.
import threading
import time
import queue
from typing import List, Dict, Any, Optional, Deque
from collections import deque
# ์กฐ๊ฑด ๋ณ์๋ฅผ ์ฌ์ฉํ ์์ฐ์-์๋น์ ํจํด
class ThreadSafeQueue:
"""์กฐ๊ฑด ๋ณ์๋ฅผ ์ฌ์ฉํ ์ค๋ ๋ ์์ ํ"""
def __init__(self, max_size: int = 5) -> None:
self.queue: Deque[Any] = deque()
self.max_size = max_size
self.condition = threading.Condition()
def put(self, item: Any) -> None:
"""์์ดํ
์ถ๊ฐ"""
with self.condition:
# ํ๊ฐ ๊ฐ๋ ์ฐผ์ผ๋ฉด ๋๊ธฐ
while len(self.queue) >= self.max_size:
print(f"ํ๊ฐ ๊ฐ๋ ์ฐธ (ํฌ๊ธฐ: {len(self.queue)}), ์์ฐ์ ๋๊ธฐ ์ค...")
self.condition.wait()
self.queue.append(item)
print(f"์์ดํ
์ถ๊ฐ: {item}, ํ ํฌ๊ธฐ: {len(self.queue)}")
# ์๋น์์๊ฒ ์๋ฆผ
self.condition.notify()
def get(self) -> Any:
"""์์ดํ
๊ฐ์ ธ์ค๊ธฐ"""
with self.condition:
# ํ๊ฐ ๋น์์ผ๋ฉด ๋๊ธฐ
while len(self.queue) == 0:
print("ํ๊ฐ ๋น์์, ์๋น์ ๋๊ธฐ ์ค...")
self.condition.wait()
item = self.queue.popleft()
print(f"์์ดํ
๊ฐ์ ธ์ด: {item}, ํ ํฌ๊ธฐ: {len(self.queue)}")
# ์์ฐ์์๊ฒ ์๋ฆผ
self.condition.notify()
return item
# ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ ์ค๋ ๋ ๋๊ธฐํ
def wait_for_event(event: threading.Event, name: str) -> None:
"""์ด๋ฒคํธ ๋๊ธฐ ํจ์"""
print(f"{name}: ์ด๋ฒคํธ ๋๊ธฐ ์ค...")
event.wait() # ์ด๋ฒคํธ๊ฐ ์ค์ ๋ ๋๊น์ง ๋๊ธฐ
print(f"{name}: ์ด๋ฒคํธ ๋ฐ์! ์์
์คํ")
# ์กฐ๊ฑด ๋ณ์ ์ฌ์ฉ ์์
queue = ThreadSafeQueue(max_size=3)
def producer() -> None:
"""์์ฐ์ ์ค๋ ๋"""
for i in range(10):
queue.put(f"์์ดํ
-{i}")
time.sleep(0.5)
def consumer() -> None:
"""์๋น์ ์ค๋ ๋"""
for _ in range(10):
item = queue.get()
# ์์ดํ
์ฒ๋ฆฌ ์๋ฎฌ๋ ์ด์
time.sleep(1)
# ์์ฐ์-์๋น์ ์ค๋ ๋ ์์
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
producer_thread.start()
consumer_thread.start()
# ์ด๋ฒคํธ ์ฌ์ฉ ์์
shutdown_event = threading.Event()
event_threads = [
threading.Thread(target=wait_for_event, args=(shutdown_event, f"Worker-{i}"))
for i in range(3)
]
for t in event_threads:
t.start()
print("๋ฉ์ธ: 3์ด ํ ์ด๋ฒคํธ ์ค์ ...")
time.sleep(3)
shutdown_event.set() # ๋ชจ๋ ๋๊ธฐ ์ค์ธ ์ค๋ ๋์๊ฒ ์ ํธ ์ ์ก
# ๋ชจ๋ ์ค๋ ๋ ์ข
๋ฃ ๋๊ธฐ
producer_thread.join()
consumer_thread.join()
for t in event_threads:
t.join()
โ ํน์ง:
- ์กฐ๊ฑด ๋ณ์(Condition)๋ฅผ ํตํ ๋๊ธฐ์ ํต์ง
- ์ด๋ฒคํธ(Event)๋ฅผ ์ฌ์ฉํ ์ค๋ ๋ ์ ํธ ์ ๋ฌ
- ์์ฐ์-์๋น์ ํจํด ๊ตฌํ
- ์ค๋ ๋ ๊ฐ ํจ์จ์ ์ธ ํต์
- ๋ด์ฅ collections.deque ํ์ฉ
๋ฆฌ์์ค ์ ๊ทผ์ ์ ํํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ํ ๋๊ธฐํ ๋ฉ์ปค๋์ฆ์ ๋๋ค.
import threading
import time
import random
from typing import List, Dict, Set, Any
# ์ธ๋งํฌ์ด๋ก ๋์ ์ ๊ทผ ์ ํ
class ConnectionPool:
"""์ธ๋งํฌ์ด๋ฅผ ์ฌ์ฉํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํ"""
def __init__(self, max_connections: int = 3) -> None:
self.semaphore = threading.Semaphore(max_connections)
self.connections: Set[int] = set()
self.lock = threading.Lock()
self.connection_count = 0
def acquire_connection(self) -> int:
"""์ฐ๊ฒฐ ํ๋"""
with self.semaphore:
# ์๊ณ ์์ญ ๋ด์์ ์ฐ๊ฒฐ ์์ฑ
with self.lock:
self.connection_count += 1
connection_id = self.connection_count
self.connections.add(connection_id)
print(f"์ฐ๊ฒฐ ํ๋: {connection_id}, ํ์ฑ ์ฐ๊ฒฐ: {len(self.connections)}")
return connection_id
def release_connection(self, connection_id: int) -> None:
"""์ฐ๊ฒฐ ๋ฐํ"""
with self.lock:
if connection_id in self.connections:
self.connections.remove(connection_id)
print(f"์ฐ๊ฒฐ ๋ฐํ: {connection_id}, ํ์ฑ ์ฐ๊ฒฐ: {len(self.connections)}")
# BoundedSemaphore ์ฌ์ฉ ์์
class ResourceManager:
"""์ ํํ ๋ฆฌ์์ค ๊ด๋ฆฌ๋ฅผ ์ํ ๋ฐ์ด๋๋ ์ธ๋งํฌ์ด"""
def __init__(self, resources: int = 5) -> None:
# BoundedSemaphore๋ ์ต๋ ์นด์ดํธ๋ฅผ ์ด๊ณผํ์ฌ releaseํ๋ฉด ValueError ๋ฐ์
self.semaphore = threading.BoundedSemaphore(resources)
self.resource_count = resources
def use_resource(self, worker_id: int) -> None:
"""๋ฆฌ์์ค ์ฌ์ฉ"""
with self.semaphore:
print(f"์์ปค {worker_id}: ๋ฆฌ์์ค ํ๋")
# ๋ฆฌ์์ค ์ฌ์ฉ ์๋ฎฌ๋ ์ด์
time.sleep(random.uniform(0.5, 1.5))
print(f"์์ปค {worker_id}: ๋ฆฌ์์ค ๋ฐํ")
# ์ธ๋งํฌ์ด ์ฌ์ฉ ์์
pool = ConnectionPool(max_connections=3)
def worker(worker_id: int) -> None:
"""์ฐ๊ฒฐ ํ์ ์ฌ์ฉํ๋ ์์
์"""
connection_id = pool.acquire_connection()
try:
# ์ฐ๊ฒฐ ์ฌ์ฉ ์๋ฎฌ๋ ์ด์
print(f"์์ปค {worker_id}: ์ฐ๊ฒฐ {connection_id} ์ฌ์ฉ ์ค")
time.sleep(random.uniform(1, 3))
finally:
# ํญ์ ์ฐ๊ฒฐ ๋ฐํ
pool.release_connection(connection_id)
print(f"์์ปค {worker_id}: ์์
์๋ฃ")
# ๋ฐ์ด๋๋ ์ธ๋งํฌ์ด ์ฌ์ฉ ์์
resource_mgr = ResourceManager(resources=2)
def resource_worker(worker_id: int) -> None:
"""๋ฆฌ์์ค ๊ด๋ฆฌ์๋ฅผ ์ฌ์ฉํ๋ ์์
์"""
for _ in range(3):
resource_mgr.use_resource(worker_id)
time.sleep(random.uniform(0.1, 0.5))
# ์ค๋ ๋ ์์ฑ ๋ฐ ์คํ
semaphore_threads = [threading.Thread(target=worker, args=(i,)) for i in range(5)]
resource_threads = [threading.Thread(target=resource_worker, args=(i,)) for i in range(3)]
for thread in semaphore_threads + resource_threads:
thread.start()
for thread in semaphore_threads + resource_threads:
thread.join()
โ ํน์ง:
- ์ธ๋งํฌ์ด๋ฅผ ํตํ ๋์ ์ ๊ทผ ์ ํ
- ๋ฐ์ด๋๋ ์ธ๋งํฌ์ด๋ก ์ ํํ ๋ฆฌ์์ค ์นด์ดํ
- ๋ฆฌ์์ค ํ ํจํด ๊ตฌํ
- ๋์์ฑ ์์ค ์ ์ด
- try/finally๋ฅผ ํตํ ๋ฆฌ์์ค ์ ๋ฆฌ ๋ณด์ฅ
concurrent.futures
๋ชจ๋์ ์ฌ์ฉํ ์ค๋ ๋ ํ ๊ธฐ๋ฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ ๋ฐฉ์์
๋๋ค.
import concurrent.futures
import threading
import requests
import time
from typing import List, Dict, Any, Callable, TypeVar, Generic
T = TypeVar('T') # ์ ๋ค๋ฆญ ํ์
๋ณ์
def download_url(url: str) -> Dict[str, Any]:
"""URL์์ ๋ฐ์ดํฐ ๋ค์ด๋ก๋"""
print(f"๋ค์ด๋ก๋ ์์: {url} (์ค๋ ๋: {threading.current_thread().name})")
start_time = time.time()
response = requests.get(url)
data = response.text
elapsed = time.time() - start_time
return {
'url': url,
'status': response.status_code,
'size': len(data),
'time': elapsed,
'thread': threading.current_thread().name
}
def process_data(data: Dict[str, Any]) -> Dict[str, Any]:
"""๋ค์ด๋ก๋ํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ"""
print(f"๋ฐ์ดํฐ ์ฒ๋ฆฌ ์์: {data['url']} (์ค๋ ๋: {threading.current_thread().name})")
# ์ฒ๋ฆฌ ์๊ฐ ์๋ฎฌ๋ ์ด์
time.sleep(0.5)
data['processed'] = True
return data
class ThreadPoolManager(Generic[T]):
"""ThreadPoolExecutor ๊ด๋ฆฌ ํด๋์ค"""
def __init__(self, max_workers: int = None) -> None:
self.max_workers = max_workers
def map_tasks(self, func: Callable[..., T], tasks: List[Any]) -> List[T]:
"""์์
๋งคํ ๋ฐ ๋ณ๋ ฌ ์คํ"""
results: List[T] = []
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
# submit ๋ฉ์๋๋ก ์์
์ ์ถ ๋ฐ Future ๊ฐ์ฒด ์์ง
future_to_task = {executor.submit(func, task): task for task in tasks}
# as_completed๋ก ์๋ฃ๋ ์์
๋ถํฐ ๊ฒฐ๊ณผ ์์ง
for future in concurrent.futures.as_completed(future_to_task):
try:
result = future.result()
results.append(result)
except Exception as exc:
task = future_to_task[future]
print(f"์์
{task} ์คํ ์ค ์ค๋ฅ ๋ฐ์: {exc}")
return results
def process_tasks(self, tasks: List[Any], task_func: Callable[[Any], T]) -> List[T]:
"""์์
์ฒ๋ฆฌ with progress callback"""
results: List[T] = []
completed = 0
total = len(tasks)
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
# submit ๋ฉ์๋๋ก ์์
์ ์ถ
futures = [executor.submit(task_func, task) for task in tasks]
# as_completed๋ก ์๋ฃ๋ ์์
๋ถํฐ ๊ฒฐ๊ณผ ์์ง
for future in concurrent.futures.as_completed(futures):
try:
result = future.result()
results.append(result)
# ์งํ ์ํฉ ์
๋ฐ์ดํธ
completed += 1
print(f"์งํ ์ํฉ: {completed}/{total} ({completed/total*100:.1f}%)")
except Exception as exc:
print(f"์์
์คํ ์ค ์ค๋ฅ ๋ฐ์: {exc}")
return results
def chain_tasks(self, initial_tasks: List[Any],
first_func: Callable[[Any], Any],
second_func: Callable[[Any], T]) -> List[T]:
"""์ฐ์ ์์
์ฒ๋ฆฌ (์ฒซ ์์
๊ฒฐ๊ณผ๋ฅผ ๋ ๋ฒ์งธ ์์
์ ์
๋ ฅ์ผ๋ก ์ฌ์ฉ)"""
intermediate_results = []
final_results: List[T] = []
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as first_executor:
# ์ฒซ ๋ฒ์งธ ๋จ๊ณ ์์
์ ์ถ
first_futures = [first_executor.submit(first_func, task) for task in initial_tasks]
# ์ฒซ ๋ฒ์งธ ์์
๊ฒฐ๊ณผ ์์ง
for future in concurrent.futures.as_completed(first_futures):
try:
result = future.result()
intermediate_results.append(result)
except Exception as exc:
print(f"์ฒซ ๋ฒ์งธ ์์
์คํ ์ค ์ค๋ฅ ๋ฐ์: {exc}")
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as second_executor:
# ๋ ๋ฒ์งธ ๋จ๊ณ ์์
์ ์ถ
second_futures = [second_executor.submit(second_func, result) for result in intermediate_results]
# ๋ ๋ฒ์งธ ์์
๊ฒฐ๊ณผ ์์ง
for future in concurrent.futures.as_completed(second_futures):
try:
result = future.result()
final_results.append(result)
except Exception as exc:
print(f"๋ ๋ฒ์งธ ์์
์คํ ์ค ์ค๋ฅ ๋ฐ์: {exc}")
return final_results
# ThreadPoolExecutor ์ฌ์ฉ ์์
urls = [
"https://www.example.com",
"https://www.google.com",
"https://www.github.com",
"https://www.python.org",
"https://www.wikipedia.org"
]
# 1. ๊ธฐ๋ณธ ThreadPoolExecutor ์ฌ์ฉ
print("1. ๊ธฐ๋ณธ ThreadPoolExecutor ์คํ")
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
# map ๋ฉ์๋๋ก ๊ฐ๋จํ๊ฒ ๋ณ๋ ฌ ์ฒ๋ฆฌ
results = list(executor.map(download_url, urls))
for result in results:
print(f"๋ค์ด๋ก๋ ์๋ฃ: {result['url']}, ํฌ๊ธฐ: {result['size']}, ์๊ฐ: {result['time']:.2f}์ด")
# 2. ThreadPoolManager ํด๋์ค ์ฌ์ฉ
print("\n2. ThreadPoolManager ํด๋์ค ์คํ")
pool_manager = ThreadPoolManager[Dict[str, Any]](max_workers=3)
# ์ฐ์ ์์
- ๋ค์ด๋ก๋ ํ ์ฒ๋ฆฌ
print("\n2.1 ์ฐ์ ์์
์คํ")
final_results = pool_manager.chain_tasks(urls, download_url, process_data)
for result in final_results:
print(f"์ฒ๋ฆฌ ์๋ฃ: {result['url']}, ์ฒ๋ฆฌ๋จ: {result.get('processed', False)}")
print("\n2.2 ๋จ์ผ ์์
์งํ๋ฅ ์ถ์ ")
download_results = pool_manager.process_tasks(urls, download_url)
print(f"๋ชจ๋ ๋ค์ด๋ก๋ ์๋ฃ. ์ด {len(download_results)}๊ฐ URL ์ฒ๋ฆฌ๋จ.")
โ ํน์ง:
- ThreadPoolExecutor๋ฅผ ํตํ ์ค๋ ๋ ํ ๊ด๋ฆฌ
- Future ๊ฐ์ฒด๋ฅผ ํตํ ๋น๋๊ธฐ ์์ ๊ด๋ฆฌ
- ์์ ์งํ ์ํฉ ์ถ์ ๊ธฐ๋ฅ
- ๋ค์ํ ๋ณ๋ ฌ ์ฒ๋ฆฌ ํจํด ์ง์
- ์ ๋ค๋ฆญ ํ์ ์ ํ์ฉํ ์ ์ฐํ ์ธํฐํ์ด์ค
- ์ฐ์ ์์ ์ฒ๋ฆฌ ํจํด
- as_completed๋ฅผ ํตํ ์๋ฃ ์์๋๋ก ๊ฒฐ๊ณผ ์ฒ๋ฆฌ
- ์์ธ ์ฒ๋ฆฌ ๋ฐ ์๋ฌ ๊ด๋ฆฌ
โ ๋ชจ๋ฒ ์ฌ๋ก:
- GIL(Global Interpreter Lock) ์ดํดํ๊ธฐ
- ํ์ด์ฌ์ GIL๋ก ์ธํด CPU ๋ฐ์ด๋ ์์ ์ ๋ฉํฐํ๋ก์ธ์ฑ์ด ๋ ํจ์จ์
- I/O ๋ฐ์ด๋ ์์ (๋คํธ์ํฌ, ํ์ผ)์ ์ค๋ ๋ฉ ํ์ฉ์ด ์ ํฉ
- ์ค๋ ๋ ์๋ช
์ฃผ๊ธฐ ๊ด๋ฆฌ
- ๋ฐ๋ชฌ ์ค๋ ๋๋ฅผ ์ ์ ํ ํ์ฉํ์ฌ ์์ ๋์ ๋ฐฉ์ง
- join()์ผ๋ก ๋ชจ๋ ์ค๋ ๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๋๊ธฐ
- ๊ณต์ ์์ ๊ด๋ฆฌ
- ๊ฒฝ์ ์กฐ๊ฑด(Race Condition)์ ๋ฐฉ์งํ๊ธฐ ์ํด Lock ์ฌ์ฉ
- ๊ณต์ ์์ ์ ๊ทผ ์ ํญ์ ๋๊ธฐํ ๋ฉ์ปค๋์ฆ ์ ์ฉ
- ๋ฐ๋๋ฝ(Deadlock) ๋ฐฉ์ง
- ๋ฝ ํ๋ ์์๋ฅผ ์ผ๊ด๋๊ฒ ์ ์ง
- ๊ฐ๋ฅํ๋ฉด with ๋ฌธ์ ์ฌ์ฉํ์ฌ ๋ฆฌ์์ค ์๋ ํด์
- ํ์์์ ์ค์ ์ผ๋ก ์์ํ ๋๊ธฐ ๋ฐฉ์ง
- ์๋ฌ ์ฒ๋ฆฌ
- ๋ชจ๋ ์ค๋ ๋์์ ์์ธ ์ฒ๋ฆฌ ๊ตฌํ
- ๋ฉ์ธ ์ค๋ ๋์์ ์์ธ ๊ฐ์ง ๋ฐ ๋ก๊น
- threading.excepthook ํ์ฉ
- ์ฑ๋ฅ ์ต์ ํ
- ๋๋ฌด ๋ง์ ์ค๋ ๋ ์์ฑ ํผํ๊ธฐ(์๋ฐฑ ๊ฐ ์ด์)
- ThreadPoolExecutor๋ก ํจ์จ์ ์ธ ์ค๋ ๋ ์ฌ์ฌ์ฉ
- CPU ๋ฐ์ด๋ ์์ ์ multiprocessing ๋ชจ๋ ๊ณ ๋ ค
- ์ค๋ ๋ ์์ (Thread-Safe) ์ค๊ณ
- ๋ถ๋ณ ๊ฐ์ฒด ์ ํธ
- ์ค๋ ๋ ๋ก์ปฌ ์ ์ฅ์(thread-local storage) ํ์ฉ
- ๊ฐ๋ฅํ๋ฉด ํ ๋ฑ์ ์ค๋ ๋ ์์ ํ ์๋ฃ๊ตฌ์กฐ ์ฌ์ฉ
- ๋๋ฒ๊น
๊ณผ ํ
์คํธ
- ์ค๋ ๋ ์ด๋ฆ ์ง์ ์ผ๋ก ๋ก๊น ๋ฐ ๋๋ฒ๊น ์ฉ์ดํ๊ฒ
- ์ค๋ ๋ ๊ฐ ๊ฒฝ์ ์กฐ๊ฑด ํ ์คํธ๋ฅผ ์ํ ์ ๋ต ์๋ฆฝ
- ๋จ์ผ ์ค๋ ๋ ๋ชจ๋๋ก ๊ฒ์ฆ ํ ๋ณ๋ ฌ ๋ชจ๋๋ก ์ ํ