دو برنامه که «همزمان» پیش میروند لزوماً «موازی» نیستند. همروندی (concurrency) یعنی مدیریت چند کارِ درهمرفته؛ موازیسازی (parallelism) یعنی اجرای واقعیِ همزمان روی چند هسته. ریسهها در پایتون برای کارِ منتظرِ I/O عالیاند، ولی بهخاطر GIL برای محاسبات سنگینِ CPU موازیِ واقعی نمیسازند.
ریسه در برابر فرایند
فرایند حافظهٔ جدا دارد و سنگین است؛ ریسه سبک است ولی حافظه را با ریسههای همفرایندش شریک میشود. همین شراکت، هم قدرت است هم دردسر: دردسرش اسم دارد — مسابقهٔ دادهای (race condition).
import threading
counter = 0
lock = threading.Lock()
def worker(n):
global counter
for _ in range(n):
with lock: # ناحیهٔ بحرانی: فقط یک ریسه در هر لحظه
counter += 1
threads = [threading.Thread(target=worker, args=(10_000,)) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print(counter) # همیشه 40000 — بدون قفل، کمتر و تصادفی بود
بخشی از کد که به حالت مشترک دسترسی دارد و نباید دو ریسه همزمان واردش شوند. قفل (Lock) ورود را یکییکی میکند.
اشتباه رایج
چون GIL هست، در پایتون هیچوقت مسابقهٔ دادهای رخ نمیدهد.
تصحیح
GIL فقط تضمین میکند بایتکدها درهم اجرا نشوند، نه اینکه عملیات چندقدمی (خواندن-تغییر-نوشتن) اتمی باشد. counter += 1 بدون قفل در CPython هم گم میکند.
بهجای قفل دستی: صف امنریسهای
بهترین همگامسازی، کمترین حالت مشترک است. الگوی تولیدکننده/مصرفکننده با queue.Queue (که خودش امنریسهای است) جای قفل دستی:
import queue, threading
jobs = queue.Queue()
for i in range(10):
jobs.put(f"job-{i}")
def consumer():
while True:
try:
job = jobs.get_nowait()
except queue.Empty:
return
print("انجام شد:", job)
jobs.task_done()
workers = [threading.Thread(target=consumer) for _ in range(3)]
for w in workers: w.start()
for w in workers: w.join()
برای کارهای «اجرا و جواب بگیر» هم concurrent.futures.ThreadPoolExecutor تمیزترین ابزار است.
شبکه: سوکت TCP
سوکت، نقطهٔ پایانیِ یک اتصال است. در TCP: سرور گوش میدهد و قبول میکند، کلاینت وصل میشود، هر دو بایت میفرستند و میگیرند، آخرش اتصال بسته میشود.
# server.py
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 65432))
s.listen()
print("سرور گوش میدهد...")
conn, addr = s.accept()
with conn:
print("وصل شد:", addr)
data = conn.recv(1024)
conn.sendall(b"echo:" + data)
# client.py
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("127.0.0.1", 65432))
s.sendall("سلام سرور".encode("utf-8"))
print(s.recv(1024).decode("utf-8"))
سنجش فهم — همروندی و شبکه
۰ از ۳ پاسخ داده شده