پاسخ تمرین ۱
GIL (مخفف Global Interpreter Lock) مکانیزمی در پایتون است که مانع از اجرای واقعاً موازی تردها میشود؛ یعنی در هر لحظه فقط یکی از تردهای یک پردازه اجازهی اجرا دارد، نه چند تا همزمان.
پاسخ تمرین ۲
حتی اگر فقط یک ترد در هر لحظه اجرا شود، جابهجایی سریع بین تردها ارزشافزوده ایجاد میکند. در دانلود منیجر، یک ترد بخشی از فایل را دانلود میکند و ترد دیگر درصد پیشرفت را بهروزرسانی میکند؛ این دو باید با هم پیش بروند. اگر برنامه تکتردی باشد، باید مدام دانلود را متوقف کند تا درصد را نمایش دهد؛ اما جابهجایی خودکار بین تردها این هماهنگی را فراهم میکند (بهویژه چون ترد در حال انتظارِ I/O، اجرا را به دیگری میدهد).
پاسخ تمرین ۳
چون افزایش یک متغیر معمولاً از سه گام جدا (خواندن مقدار فعلی، تغییر آن، نوشتن مقدار جدید) تشکیل شده است. GIL از اجرای موازی جلوگیری میکند اما میتواند وسط این سه گام ترد را عوض کند و اجازهی دسترسی به همان متغیر را به ترد دیگری بدهد. برای جلوگیری از این تداخل باید این مراحل را با یک قفل بهصورت اتمیک اجرا کرد.
پاسخ تمرین ۴
هدف اصلی سازندگان پایتون سادگی بود؛ GIL مدیریت تردها و جلوگیری از تداخل درون مفسر را سادهتر میکند. غیرفعالکردن آن (از پایتون ۳.۱۳ ممکن است) خطرناک تلقی میشود چون مسئولیت کاملِ هماهنگی بین تردها و جلوگیری از تداخل به عهدهی برنامهنویس میافتد، که به دانش عمیق در برنامهنویسی چندتردی نیاز دارد.
پاسخ تمرین ۵
کار I/O-bound بیشترِ وقتش را صرف انتظار میکند (شبکه، فایل، دیتابیس)؛ اینجا threading مناسب است چون ترد در حال انتظار GIL را آزاد میکند و ترد دیگر کار میکند. کار CPU-bound تماموقت پردازنده را درگیر میکند؛ اینجا تردها بهخاطر GIL سریعتر نمیشوند و باید از multiprocessing استفاده کرد که هر پردازه GIL مستقل خودش را دارد.
پاسخ تمرین ۶
import time
from threading import Thread
def fetch():
time.sleep(2) # mostly waiting (I/O)
start = time.time()
threads = [Thread(target=fetch) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print("%.1fs" % (time.time() - start)) # ~2s, not ~8sپاسخ تمرین ۷
import time
from multiprocessing import Process
def heavy():
s = 0
for i in range(20_000_000):
s += i
if __name__ == '__main__':
start = time.time()
p1 = Process(target=heavy)
p2 = Process(target=heavy)
p1.start(); p2.start()
p1.join(); p2.join()
print("%.2fs" % (time.time() - start)) # real parallelism across coresراهحل جایگزین: استفاده از concurrent.futures.ProcessPoolExecutor که مدیریت پردازهها را سادهتر میکند.