قفل مفسر جهانی پایتون (GIL) یک نوع قفل فرآیند است که توسط پایتون هر زمان که با فرآیندها سروکار دارد، استفاده میشود. به طور کلی، پایتون فقط از یک رشته (thread) برای اجرای مجموعه دستورات نوشته شده استفاده میکند. این به این معناست که در پایتون، تنها یک رشته در هر لحظه اجرا خواهد شد. عملکرد فرآیند تکرشتهای و فرآیند چندرشتهای در پایتون یکسان است و این به دلیل وجود GIL در پایتون است. ما نمیتوانیم به چندرشتهای در پایتون دست یابیم زیرا Global Interpreter Lock (GIL) از اجرای چندین رشته جلوگیری میکند و آن را به یک رشته محدود میکند.
مشکلی که GIL برای پایتون حل کرد:
پایتون چیزی دارد که هیچ زبان دیگری ندارد و آن شمارنده مراجع است. با کمک شمارنده مراجع، میتوانیم تعداد کل مراجعاتی که به صورت داخلی در پایتون برای اختصاص دادن یک مقدار به یک شیء داده انجام شده را بشماریم. به لطف این شمارنده، میتوانیم مراجع را بشماریم و زمانی که این تعداد به صفر برسد، متغیر یا شیء داده به طور خودکار آزاد خواهد شد. برای مثال برنامه زیر تعداد مراجعات را نمایش میدهد:
python
import sys
raadino_var = "Raad"
print(sys.getrefcount(raadino_var))
string_rad = raadino_var
print(sys.getrefcount(string_rad))
خروجی:
4
5
این متغیر شمارنده مراجع باید محافظت شود، زیرا گاهی دو رشته به طور همزمان مقدار آن را افزایش یا کاهش میدهند و این ممکن است منجر به نشت حافظه شود. بنابراین، برای محافظت از رشتهها، قفلهایی به تمام ساختارهای داده که بین رشتهها به اشتراک گذاشته میشوند اضافه کردیم. اما گاهی با افزودن قفلها، مشکل دیگری به نام بنبست (deadlock) ایجاد میشود. برای جلوگیری از نشت حافظه و مشکلات بنبست، یک قفل واحد به نام قفل مفسر سراسری (GIL) به مفسر اضافه کردیم.
چرا GIL به عنوان راهحل انتخاب شد:
پایتون از زبان C در بکاند خود پشتیبانی میکند و بیشتر کتابخانههای مرتبط با پایتون عمدتاً به زبان C و C++ نوشته شدهاند. برای آشنایی بیشتر با تاریخچه پایتون میتوانید ورژنهای پایتون در گذر زمان را نیز بخوانید. به دلیل وجود GIL، پایتون روش بهتری برای مدیریت حافظه ایمن در برابر رشتهها فراهم میکند. قفل مفسر جهانی (GIL) به راحتی در پایتون پیادهسازی شده است چون تنها به یک قفل واحد برای پردازش رشتهها نیاز دارد. GIL ساده است و به راحتی به پایتون اضافه شد. همچنین برای برنامههای تکرشتهای بهبود عملکرد ایجاد میکند زیرا تنها یک قفل نیاز به مدیریت دارد.
تاثیر بر برنامههای چندرشتهای پایتون:
هنگامی که کاربر برنامههای پایتون یا هر برنامه کامپیوتری دیگری را مینویسد، تفاوتی بین برنامههایی که از لحاظ عملکرد به پردازنده (CPU) متکی هستند و آنهایی که به ورودی و خروچی (I/O) وابسته هستند وجود دارد. CPU برنامه را با اجرای چندین عملیات همزمان به محدودیتهای خود میرساند، در حالی که برنامههای I/O باید زمانی را منتظر ورودی/خروجی بگذرانند. به عنوان مثال:
کد 1: برنامهای که به CPU وابسته است و یک شمارش معکوس ساده را انجام میدهد
python
# برنامه پایتون که برنامهای وابسته به CPU را نشان میدهد
import time
from threading import Thread
COUNT = 50000000
def countdown(n):
while n > 0:
n -= 1
start = time.time()
countdown(COUNT)
end = time.time()
print('Time taken in seconds -', end - start)
خروجی:
Time taken in seconds - 2.5236213207244873
کد 2: دو رشته که به صورت موازی اجرا میشوند
python
# برنامه پایتون که دو رشته را به صورت موازی نشان میدهد
import time
from threading import Thread
COUNT = 50000000
def countdown(n):
while n > 0:
n -= 1
t1 = Thread(target = countdown, args =(COUNT//2, ))
t2 = Thread(target = countdown, args =(COUNT//2, ))
start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()
print('Time taken in seconds -', end - start)
خروجی:
Time taken in seconds - 2.183610439300537
همانطور که میبینید، در کدهای فوق که فرآیند وابسته به CPU و فرآیند چندرشتهای دارند، عملکرد آنها مشابه است زیرا در برنامه وابسته به CPU، GIL اجازه نمیدهد که CPU همزمان با چندین رشته کار کند. تاثیر GIL بر روی فرآیندهای وابسته به CPU و چندرشتهای در پایتون یکسان است.
چرا GIL هنوز حذف نشده است ؟
GIL هنوز بهبود نیافته است زیرا پیادهسازی GIL در پایتون 2 وجود دارد و اگر این را در پایتون 3 تغییر دهیم، ممکن است مشکلاتی ایجاد شود. بنابراین، به جای حذف GIL، این مفهوم را بهبود دادهایم. یکی از دلایل عدم حذف GIL این است که پایتون به شدت به C در بکاند وابسته است و افزونههای C به شدت به روشهای پیادهسازی GIL وابسته هستند. اگرچه روشهای دیگری برای حل مشکلاتی که GIL حل میکند وجود دارد، بیشتر آنها پیچیده هستند و میتوانند سیستم را کند کنند.
چگونه با GIL در پایتون مقابله کنیم ؟
بیشتر مواقع ما از multiprocessing برای جلوگیری از تاثیر GIL استفاده میکنیم. در این پیادهسازی، پایتون یک مفسر جداگانه به هر فرآیند اختصاص میدهد، بنابراین در این حالت، یک رشته واحد به هر فرآیند در multiprocessing اختصاص داده میشود.
python
# برنامه پایتون که multiprocessing را نشان میدهد
import multiprocessing
import time
COUNT = 50000000
def countdown(n):
while n > 0:
n -= 1
if __name__ == "__main__":
# ایجاد فرآیندها
start = time.time()
p1 = multiprocessing.Process(target = countdown, args =(COUNT//2, ))
p2 = multiprocessing.Process(target = countdown, args =(COUNT//2, ))
# شروع فرآیند 1
p1.start()
# شروع فرآیند 2
p2.start()
# انتظار برای اتمام فرآیند 1
p1.join()
# انتظار برای اتمام فرآیند 2
p2.join()
end = time.time()
print('Time taken in seconds -', end - start)
خروجی:
Time taken in seconds - 2.5148496627807617
همانطور که میبینید، تفاوتی بین زمان لازم برای سیستم چندرشتهای و سیستم multiprocessing وجود ندارد. این به این دلیل است که سیستم multiprocessing نیز مشکلات خاص خود را دارد. بنابراین، این روش مشکل را حل نمیکند، اما یک راهحل ارائه میدهد که GIL اجازه میدهد در پایتون اجرا شود.
ورود و ثبت نام برای ارسال نظر وارد شوید
¶ به نظر شما GIL همچنان برای پایتون ضروری است یا باید از آن خلاص شد؟
تجربه شما از زبانهای دیگری که GIL ندارند، در مقایسه با پایتون چه بوده است؟
¶ GIL در پایتون عمدتاً به دلیل سادگی در مدیریت حافظه و جلوگیری از مشکلات همزمانی (concurrency) ضروری باقی مانده است. از بین بردن GIL پیچیدگیهای زیادی را به همراه خواهد داشت، زیرا مدل مدیریت حافظه پایتون باید تغییرات اساسی کند.
زبانهایی مانند Go، Rust، C++ یا Java که GIL ندارند، به طور موثری از چندنخی(multi thread) استفاده میکنند و میتوانند از چند هسته پردازشی به طور همزمان بهرهبرداری کنند. تجربه کار با این زبانها در پروژههای سنگین به مراتب بهتر است
ورود و ثبت نام برای ارسال نظر وارد شوید