concurrent.interpreters --- چندین مفسر در یک فرایند واحد

اضافه شده در نسخه‌ی 3.14.

کد منبع: Lib/concurrent/interpreters


ماژول concurrent.interpreters رابط‌های سطح بالاتر را بر فراز ماژول سطح پایین‌تر _interpreters ایجاد می‌کند.

این ماژول عمدتاً برای ارائه یک API پایه برای مدیریت مفسرها (معروف به «subinterpreters») و اجرای موارد در آن‌ها در نظر گرفته شده است. اجرا عمدتاً شامل انتقال به یک مفسر (در نخ جاری) و فراخوانی یک تابع در آن زمینه اجرا است.

برای همروندی، خود مفسرها (و این ماژول) چیز چندانی بیشتر از جداسازی فراهم نمی‌کنند، که به‌تنهایی مفید نیست. همروندی واقعی به‌صورت جداگانه از طریق نخ‌ها در دسترس است -- پایین را ببینید.

همچنین ملاحظه نمائید

InterpreterPoolExecutor

نخ‌ها را با مفسرها در یک رابط آشنا ترکیب می‌کند.

جداسازی ماژول‌های توسعه

چگونگی به‌روزرسانی یک ماژول توسعه برای پشتیبانی از چندین مفسر.

PEP 554

PEP 734

PEP 684

دسترس‌پذیری: not WASI.

این ماژول روی WebAssembly کار نمی‌کند یا در دسترس نیست. برای اطلاعات بیشتر سکوهای WebAssembly را ببینید.

جزئیات کلیدی

پیش از آنکه بیشتر وارد بحث شویم، تعداد اندکی جزئیات وجود دارد که باید درباره‌ی استفاده از چندین مفسر در نظر داشته باشید:

  • ایزوله، به‌طور پیش‌فرض

  • بدون نخ‌های ضمنی

  • هنوز همه‌ی بسته‌های PyPI از استفاده در چندین مفسر پشتیبانی نمی‌کنند

مقدمه

یک «مفسر» عملاً زمینه اجرای ران‌تایم پایتون است. این شامل تمام وضعیت مورد نیاز ران‌تایم برای اجرای یک برنامه است. این شامل مواردی مانند وضعیت ایمپورت و توکارها می‌شود. (هر نخ، حتی اگر فقط نخ اصلی وجود داشته باشد، علاوه بر مفسر فعلی، دارای وضعیت اضافی ران‌تایم مربوط به استثنای فعلی و حلقه ارزیابی بایت‌کد است.)

مفهوم و کارکرد مفسر از نسخه‌ی 2.2 بخشی از پایتون بوده است، اما این قابلیت تنها از طریق C-API در دسترس بود و چندان شناخته‌شده نبود، و جداسازی تا نسخه‌ی 3.12 نسبتاً ناقص بود.

مفسرهای متعدد و جداسازی

یک پیاده‌سازی پایتون ممکن است از استفاده از چندین مفسر در یک فرایند پشتیبانی کند. CPython این پشتیبانی را دارد. هر مفسر عملاً از سایر مفسرها ایزوله است (با تعداد محدودی استثنا بر این قاعده، که سراسری در سطح فرایند هستند و به‌دقت مدیریت می‌شوند).

آن جداسازی عمدتاً به‌عنوان یک تفکیک قوی میان کامپوننت‌های منطقی مجزای یک برنامه مفید است، جایی که می‌خواهید کنترل دقیقی بر نحوه تعامل آن کامپوننت‌ها داشته باشید.

توجه

مفسرهای موجود در یک فرایند یکسان، از نظر فنی هرگز نمی‌توانند به‌طور کامل از یکدیگر جدا شوند، زیرا محدودیت‌های اندکی برای دسترسی به حافظه در همان فرایند وجود دارد. ران‌تایم پایتون نهایت تلاش خود را برای جداسازی به کار می‌گیرد، اما ماژول‌های توسعه ممکن است به‌راحتی آن را نقض کنند. بنابراین، در موقعیت‌های حساس امنیتی، جایی که نباید به داده‌های یکدیگر دسترسی داشته باشند، از چندین مفسر استفاده نکنید.

اجرا در مفسر

اجرا در یک مفسر دیگر، مستلزم تعویض به آن در نخ جاری و سپس فراخوانی یک تابع است. ران‌تایم تابع را با استفاده از وضعیت مفسر جاری اجرا می‌کند. ماژول concurrent.interpreters یک API پایه برای ایجاد و مدیریت مفسرها و همچنین عملیات تعویض و فراخوانی (switch-and-call) ارائه می‌کند.

هیچ نخ دیگری به‌طور خودکار برای این عملیات آغاز نمی‌شود. با این حال، برای این کار یک تابع کمکی وجود دارد. یک تابع کمکی اختصاصی دیگر برای فراخوانی exec() توکار در یک مفسر وجود دارد.

هنگامی که exec() (یا eval()) در یک مفسر فراخوانی می‌شوند، با استفاده از ماژول __main__ مفسر به‌عنوان فضای نام «globals» اجرا می‌شوند. همین موضوع برای توابعی که با هیچ ماژولی مرتبط نیستند نیز صادق است. این مشابه روشی است که اسکریپت‌های فراخوانی‌شده از خط فرمان در ماژول __main__ اجرا می‌شوند.

همروندی و موازی‌سازی

همان‌طور که پیش‌تر گفته شد، مفسرها به‌خودی خود هیچ‌گونه همروندی‌ای فراهم نمی‌کنند. آن‌ها صرفاً نمایانگر زمینه اجرای جداشده‌ای هستند که ران‌تایم در نخ جاری از آن استفاده خواهد کرد. این جداسازی آن‌ها را شبیه به فرآیندها می‌کند، اما همچنان مانند نخ‌ها از کارایی درون‌فرآیندی برخوردارند.

با همه این اوصاف، مفسرها به‌طور طبیعی از برخی سبک‌های هم‌روندی پشتیبانی می‌کنند. آن جداسازی اثر جانبی قدرتمندی دارد. این امر رویکرد متفاوتی به هم‌روندی را ممکن می‌سازد، نسبت به آنچه می‌توانید با ناهمگام یا نخ‌ها در پیش بگیرید. این مدل هم‌روندی مشابه CSP یا مدل کنشگر (actor model) است؛ مدلی که استدلال درباره آن نسبتاً آسان است.

شما می‌توانید از آن مدل همروندی در یک نخ واحد بهره ببرید و به سبک Stackless، به‌صورت رفت‌وبرگشتی بین مفسرها جابه‌جا شوید. با این حال، این مدل زمانی مفیدتر است که مفسرها را با چندین نخ ترکیب کنید. این کار عمدتاً شامل شروع یک نخ جدید است، که در آن به مفسر دیگری جابه‌جا می‌شوید و آنچه را می‌خواهید در آنجا اجرا می‌کنید.

هر نخ واقعی در پایتون، حتی اگر فقط در نخ اصلی در حال اجرا باشید، زمینه‌ی اجرای فعلی خود را دارد. چندین نخ می‌توانند از یک مفسر مشترک یا مفسرهای متفاوت استفاده کنند.

در سطح بالا، می‌توانید ترکیب نخ‌ها و مفسرها را به‌عنوان نخ‌هایی با اشتراک‌گذاری انتخابی در نظر بگیرید.

به‌عنوان یک مزیت قابل‌توجه، مفسرها به‌اندازه‌ای از یکدیگر ایزوله هستند که GIL را به اشتراک نمی‌گذارند؛ این بدان معناست که ترکیب نخ‌ها با چندین مفسر، موازی‌سازی کامل چندهسته‌ای را ممکن می‌سازد. (این حالت از پایتون 3.12 برقرار بوده است.)

ارتباط میان مفسرها

در عمل، چندین مفسر تنها زمانی مفید هستند که راهی برای برقراری ارتباط بین آن‌ها داشته باشیم. این موضوع معمولاً شامل نوعی پیام‌رسانی می‌شود، اما حتی می‌تواند به معنای اشتراک‌گذاری داده‌ها به شیوه‌ای با مدیریت دقیق باشد.

با در نظر گرفتن این موضوع، ماژول concurrent.interpreters یک پیاده‌سازی از queue.Queue را ارائه می‌دهد که از طریق create_queue() در دسترس است.

«اشتراک‌گذاری» اشیاء

هر داده‌ای که واقعاً بین مفسرها به اشتراک گذاشته می‌شود، ایمنی نخی فراهم‌شده از سوی GIL را از دست می‌دهد. گزینه‌های مختلفی برای مدیریت این موضوع در ماژول‌های توسعه وجود دارد. با این حال، از دید کد پایتون، فقدان ایمنی نخی به این معناست که اشیاء، به‌جز چند استثنا، عملاً نمی‌توانند به اشتراک گذاشته شوند. در عوض، باید یک رونوشت ایجاد شود، که به این معناست که اشیاء تغییرپذیر همگام باقی نخواهند ماند.

به‌طور پیش‌فرض، بیشتر اشیاء هنگامی که به مفسر دیگری منتقل می‌شوند، با pickle کپی می‌شوند. تقریباً همه‌ی اشیاء تغییرناپذیر توکار یا مستقیماً به اشتراک گذاشته می‌شوند یا به‌صورت کارآمد کپی می‌شوند. برای مثال:

There are a small number of Python types that actually share mutable data between interpreters:

مرجع

این ماژول توابع زیر را تعریف می‌کند:

concurrent.interpreters.list_all()

یک list از اشیاء Interpreter برمی‌گرداند، یکی برای هر مفسر موجود.

concurrent.interpreters.get_current()

یک شیء Interpreter برای مفسر در حال اجرا برمی‌گرداند.

concurrent.interpreters.get_main()

یک شیء Interpreter برای مفسر اصلی بازمی‌گرداند. این همان مفسری است که ران‌تایم برای اجرای REPL یا اسکریپت داده‌شده در خط فرمان ایجاد کرده است. معمولاً تنها مفسر همین است.

concurrent.interpreters.create()

یک مفسر پایتون جدید (بی‌کار) را راه‌اندازی می‌کند و یک شیء Interpreter برای آن برمی‌گرداند.

concurrent.interpreters.create_queue()

یک صف بین‌مفسری جدید را مقداردهی اولیه می‌کند و یک شیء Queue برای آن برمی‌گرداند.

اشیای مفسر

class concurrent.interpreters.Interpreter(id)

یک مفسر واحد در فرایند جاری.

به‌طور کلی، Interpreter نباید مستقیماً فراخوانی شود. در عوض، از create() یا یکی دیگر از توابع ماژول استفاده کنید.

id

(فقط‌خواندنی)

شناسه‌ی مفسر زیربنایی.

whence

(فقط‌خواندنی)

رشته‌ای که منشأ مفسر را توصیف می‌کند.

is_running()

اگر مفسر هم‌اکنون در حال اجرای کد در ماژول __main__ خود باشد، True و در غیر این صورت False برمی‌گرداند.

close()

نهایی‌سازی و نابود کردن مفسر.

prepare_main(ns=None, **kwargs)

اشیاء را در ماژول __main__ مفسر مقید کنید.

برخی از شیء‌ها در واقع به اشتراک گذاشته می‌شوند و برخی به‌صورت کارآمد کپی می‌شوند، اما بیشتر آن‌ها از طریق pickle کپی می‌شوند. «اشتراک‌گذاری» اشیاء را ببینید.

exec(code, /, dedent=True)

کد منبع داده‌شده را در مفسر (در نخ فعلی) اجرا کنید.

call(callable, /, *args, **kwargs)

Return the result of running the given function in the interpreter (in the current thread).

call_in_thread(callable, /, *args, **kwargs)

تابع داده‌شده را در مفسر اجرا کنید (در یک نخ جدید).

استثناها

exception concurrent.interpreters.InterpreterError

این استثنا، زیرکلاسی از Exception، زمانی پرتاب می‌شود که خطای مرتبط با مفسر رخ دهد.

exception concurrent.interpreters.InterpreterNotFoundError

این استثنا، زیرکلاسی از InterpreterError، زمانی پرتاب می‌شود که مفسر هدف دیگر وجود ندارد.

exception concurrent.interpreters.ExecutionFailed

این استثنا، که زیرکلاسی از InterpreterError است، زمانی پرتاب می‌شود که کد در حال اجرا، استثنای گرفته‌نشده‌ای را پرتاب کرده باشد.

excinfo

یک تصویر لحظه‌ای ساده از استثنای پرتاب‌شده در مفسر دیگر.

exception concurrent.interpreters.NotShareableError

این استثنا، که زیرکلاسی از TypeError است، زمانی پرتاب می‌شود که نتوان یک شیء را به مفسر دیگری ارسال کرد.

ارتباط بین مفسرها

class concurrent.interpreters.Queue(id)

پوششی برای یک صف بین‌مفسری سطح پایین، که رابط queue.Queue را پیاده‌سازی می‌کند. صف زیرین فقط از طریق create_queue() قابل ایجاد است.

برخی از شیء‌ها در واقع به اشتراک گذاشته می‌شوند و برخی به‌صورت کارآمد کپی می‌شوند، اما بیشتر آن‌ها از طریق pickle کپی می‌شوند. «اشتراک‌گذاری» اشیاء را ببینید.

id

(فقط‌خواندنی)

شناسه‌ی صف.

exception concurrent.interpreters.QueueEmptyError

این استثنا، زیرکلاسی از queue.Empty، زمانی از Queue.get() و Queue.get_nowait() پرتاب می‌شود که صف خالی باشد.

exception concurrent.interpreters.QueueFullError

این استثنا، زیرکلاسی از queue.Full، هنگامی از Queue.put() و Queue.put_nowait() پرتاب می‌شود که صف پر باشد.

استفاده‌ی پایه

ایجاد یک مفسر و اجرای کد در آن:

from concurrent import interpreters

interp = interpreters.create()

# Run in the current OS thread.

interp.exec('print("spam!")')

interp.exec("""if True:
    print('spam!')
    """)

from textwrap import dedent
interp.exec(dedent("""
    print('spam!')
    """))

def run(arg):
    return arg

res = interp.call(run, 'spam!')
print(res)

def run():
    print('spam!')

interp.call(run)

# Run in new OS thread.

t = interp.call_in_thread(run)
t.join()