Nhiều trình thông dịch trong quy trình Python

Mặc dù trong hầu hết các mục đích sử dụng, bạn sẽ chỉ nhúng một trình thông dịch Python duy nhất, nhưng có những trường hợp bạn cần tạo một số trình thông dịch độc lập trong cùng một quy trình và thậm chí có thể trong cùng một luồng. Phiên dịch viên phụ cho phép bạn làm điều đó.

Trình thông dịch "chính" là trình thông dịch đầu tiên được tạo khi thời gian chạy khởi chạy. Nó thường là trình thông dịch Python duy nhất trong một tiến trình. Không giống như trình thông dịch phụ, trình thông dịch chính có các trách nhiệm chung về quy trình như xử lý tín hiệu. Nó cũng chịu trách nhiệm thực thi trong quá trình khởi tạo thời gian chạy và thường là trình thông dịch tích cực trong quá trình hoàn thiện thời gian chạy. Hàm PyInterpreterState_Main() trả về trạng thái của con trỏ.

Bạn có thể chuyển đổi giữa các trình thông dịch phụ bằng chức năng PyThreadState_Swap(). Bạn có thể tạo và hủy chúng bằng các chức năng sau:

type PyInterpreterConfig

Cấu trúc chứa hầu hết các tham số để định cấu hình trình thông dịch phụ. Các giá trị của nó chỉ được sử dụng trong Py_NewInterpreterFromConfig() và không bao giờ được sửa đổi trong thời gian chạy.

Added in version 3.12.

Các trường cấu trúc:

int use_main_obmalloc

Nếu đây là 0 thì trình thông dịch phụ sẽ sử dụng trạng thái cấp phát "đối tượng" của chính nó. Nếu không nó sẽ sử dụng (chia sẻ) trình thông dịch chính.

Nếu đây là 0 thì check_multi_interp_extensions phải là 1 (khác 0). Nếu đây là 1 thì gil không được là PyInterpreterConfig_OWN_GIL.

int allow_fork

Nếu đây là 0 thì thời gian chạy sẽ không hỗ trợ phân nhánh quy trình trong bất kỳ luồng nào mà trình thông dịch phụ hiện đang hoạt động. Nếu không thì ngã ba không bị hạn chế.

Lưu ý rằng mô-đun subprocess vẫn hoạt động khi không cho phép fork.

int allow_exec

Nếu đây là 0 thì thời gian chạy sẽ không hỗ trợ thay thế quy trình hiện tại thông qua exec (ví dụ: os.execv()) trong bất kỳ luồng nào mà trình thông dịch phụ hiện đang hoạt động. Nếu không thì exec không bị hạn chế.

Lưu ý rằng mô-đun subprocess vẫn hoạt động khi lệnh thực thi không được phép.

int allow_threads

Nếu đây là 0 thì mô-đun threading của trình thông dịch phụ sẽ không tạo chuỗi. Nếu không chủ đề được cho phép.

int allow_daemon_threads

Nếu đây là 0 thì mô-đun threading của trình thông dịch phụ sẽ không tạo chuỗi daemon. Mặt khác, các luồng daemon được cho phép (miễn là allow_threads khác 0).

int check_multi_interp_extensions

Nếu đây là 0 thì tất cả các mô-đun mở rộng có thể được nhập, bao gồm các mô-đun cũ (init một pha), trong bất kỳ luồng nào mà trình thông dịch phụ hiện đang hoạt động. Mặt khác, chỉ có thể nhập các mô-đun mở rộng init nhiều pha (xem PEP 489). (Xem thêm Py_mod_multiple_interpreters.)

Đây phải là 1 (khác 0) nếu use_main_obmalloc0.

int gil

Điều này xác định hoạt động của GIL cho trình thông dịch phụ. Nó có thể là một trong những điều sau đây:

PyInterpreterConfig_DEFAULT_GIL

Sử dụng lựa chọn mặc định (PyInterpreterConfig_SHARED_GIL).

PyInterpreterConfig_SHARED_GIL

Sử dụng (chia sẻ) GIL của trình thông dịch chính.

PyInterpreterConfig_OWN_GIL

Sử dụng GIL của phiên dịch viên phụ.

Nếu đây là PyInterpreterConfig_OWN_GIL thì PyInterpreterConfig.use_main_obmalloc phải là 0.

PyStatus Py_NewInterpreterFromConfig(PyThreadState **tstate_p, const PyInterpreterConfig *config)

Tạo một thông dịch viên phụ mới. Đây là một môi trường (gần như) hoàn toàn riêng biệt để thực thi mã Python. Đặc biệt, trình thông dịch mới có các phiên bản độc lập, riêng biệt của tất cả các mô-đun đã nhập, bao gồm các mô-đun cơ bản builtins, __main__sys. Bảng mô-đun được tải (sys.modules) và đường dẫn tìm kiếm mô-đun (sys.path) cũng tách biệt. Môi trường mới không có biến sys.argv. Nó có các đối tượng tệp luồng I/O tiêu chuẩn mới sys.stdin, sys.stdoutsys.stderr (tuy nhiên, những đối tượng này đề cập đến cùng một bộ mô tả tệp cơ bản).

Zz000zz đã cho sẽ kiểm soát các tùy chọn mà trình thông dịch được khởi tạo.

Sau khi thành công, tstate_p sẽ được đặt thành thread state đầu tiên được tạo trong trình thông dịch phụ mới. Trạng thái chủ đề này là attached. Lưu ý rằng không có luồng thực sự nào được tạo; xem cuộc thảo luận về trạng thái chủ đề bên dưới. Nếu việc tạo trình thông dịch mới không thành công, tstate_p được đặt thành NULL; không có ngoại lệ nào được đặt vì trạng thái ngoại lệ được lưu trữ trong attached thread state, trạng thái này có thể không tồn tại.

Giống như tất cả các hàm API khác của Python/C, attached thread state phải có trước khi gọi hàm này, nhưng nó có thể bị tách ra khi quay trở lại. Nếu thành công, trạng thái luồng được trả về sẽ là attached. Nếu trình thông dịch phụ được tạo bằng GIL của riêng nó thì attached thread state của trình thông dịch đang gọi sẽ bị tách ra. Khi hàm trả về, thread state của trình thông dịch mới sẽ là attached cho chuỗi hiện tại và attached thread state của trình thông dịch trước đó sẽ vẫn được tách ra.

Added in version 3.12.

Các phiên dịch viên phụ hoạt động hiệu quả nhất khi được tách biệt với nhau, với một số chức năng nhất định bị hạn chế:

Cấu hình PyInterpreterConfig = {
    .use_main_obmalloc = 0,
    .allow_fork = 0,
    .allow_exec = 0,
    .allow_threads = 1,
    .allow_daemon_threads = 0,
    .check_multi_interp_extensions = 1,
    .gil = PyInterpreterConfig_OWN_GIL,
};
PyThreadState *tstate = NULL;
Trạng thái PyStatus = Py_NewInterpreterFromConfig(&tstate, &config);
if (PyStatus_Exception(trạng thái)) {
    Py_ExitStatusException(trạng thái);
}

Lưu ý rằng cấu hình chỉ được sử dụng trong thời gian ngắn và không được sửa đổi. Trong quá trình khởi tạo, các giá trị của cấu hình được chuyển đổi thành các giá trị PyInterpreterState khác nhau. Bản sao cấu hình chỉ đọc có thể được lưu trữ nội bộ trên PyInterpreterState.

Các mô-đun mở rộng được chia sẻ giữa các trình thông dịch (phụ) như sau:

  • Đối với các mô-đun sử dụng khởi tạo nhiều pha, ví dụ: PyModule_FromDefAndSpec(), một đối tượng mô-đun riêng biệt sẽ được tạo và khởi tạo cho mỗi trình thông dịch. Chỉ các biến tĩnh và toàn cục cấp C được chia sẻ giữa các đối tượng mô-đun này.

  • Đối với các mô-đun sử dụng single-phase initialization cũ, ví dụ: PyModule_Create(), lần đầu tiên một tiện ích mở rộng cụ thể được nhập, nó sẽ được khởi tạo bình thường và một bản sao (nông) của từ điển của mô-đun sẽ bị cất đi. Khi cùng một tiện ích mở rộng được nhập bởi một trình thông dịch (phụ) khác, một mô-đun mới sẽ được khởi tạo và chứa đầy nội dung của bản sao này; chức năng init của tiện ích mở rộng không được gọi. Do đó, các đối tượng trong từ điển của mô-đun sẽ được chia sẻ giữa các trình thông dịch (phụ), điều này có thể gây ra hành vi không mong muốn (xem Bugs and caveats bên dưới).

    Lưu ý rằng điều này khác với những gì xảy ra khi một tiện ích mở rộng được nhập sau khi trình thông dịch đã được khởi tạo lại hoàn toàn bằng cách gọi Py_FinalizeEx()Py_Initialize(); trong trường hợp đó, hàm initmodule của tiện ích mở rộng is được gọi lại. Giống như khởi tạo nhiều pha, điều này có nghĩa là chỉ các biến tĩnh và toàn cục cấp C được chia sẻ giữa các mô-đun này.

PyThreadState *Py_NewInterpreter(void)
Một phần của ABI ổn định.

Tạo một thông dịch viên phụ mới. Về cơ bản, đây chỉ là một trình bao bọc xung quanh Py_NewInterpreterFromConfig() với cấu hình duy trì hành vi hiện có. Kết quả là một trình thông dịch phụ không bị cô lập chia sẻ GIL của trình thông dịch chính, cho phép fork/exec, cho phép các luồng daemon và cho phép các mô-đun init một pha.

void Py_EndInterpreter(PyThreadState *tstate)
Một phần của ABI ổn định.

Phá hủy trình thông dịch (phụ) được đại diện bởi thread state đã cho. Trạng thái luồng đã cho phải là attached. Khi gọi lại sẽ không có attached thread state. Tất cả các trạng thái luồng liên kết với trình thông dịch này đều bị hủy.

Py_FinalizeEx() sẽ hủy tất cả các trình thông dịch phụ chưa bị hủy rõ ràng tại thời điểm đó.

Một phiên dịch viên GIL

Added in version 3.12.

Sử dụng Py_NewInterpreterFromConfig(), bạn có thể tạo một trình thông dịch phụ tách biệt hoàn toàn với các trình thông dịch khác, bao gồm cả việc có GIL riêng. Lợi ích quan trọng nhất của sự cô lập này là trình thông dịch như vậy có thể thực thi mã Python mà không bị chặn bởi các trình thông dịch khác hoặc chặn bất kỳ trình thông dịch nào khác. Do đó, một quy trình Python thực sự có thể tận dụng được nhiều lõi CPU khi chạy mã Python. Sự cô lập cũng khuyến khích một cách tiếp cận khác để xử lý đồng thời hơn là chỉ sử dụng các luồng. (Xem PEP 554PEP 684.)

Việc sử dụng một trình thông dịch biệt lập đòi hỏi phải thận trọng trong việc duy trì sự cô lập đó. Điều đó đặc biệt có nghĩa là không chia sẻ bất kỳ đối tượng hoặc trạng thái có thể thay đổi nào mà không đảm bảo về tính an toàn của luồng. Ngay cả những đối tượng không thể thay đổi (ví dụ: None, (1, 5)) thường không thể được chia sẻ do số tiền được tính lại. Một cách tiếp cận đơn giản nhưng kém hiệu quả hơn trong vấn đề này là sử dụng khóa chung cho tất cả việc sử dụng một số trạng thái (hoặc đối tượng). Ngoài ra, các đối tượng bất biến hiệu quả (như số nguyên hoặc chuỗi) có thể được đảm bảo an toàn bất chấp việc chúng bị tính lại bằng cách đặt chúng thành immortal. Trên thực tế, điều này đã được thực hiện cho các đơn vị dựng sẵn, số nguyên nhỏ và một số đối tượng dựng sẵn khác.

Nếu bạn duy trì sự cô lập thì bạn sẽ có quyền truy cập vào điện toán đa lõi thích hợp mà không gặp phải những rắc rối khi phân luồng tự do. Việc không duy trì sự cô lập sẽ khiến bạn phải gánh chịu toàn bộ hậu quả của việc phân luồng tự do, bao gồm cả sự chạy đua và sự cố khó gỡ lỗi.

Ngoài ra, một trong những thách thức chính của việc sử dụng nhiều phiên dịch viên cách ly là làm thế nào để giao tiếp giữa chúng một cách an toàn (không phá vỡ sự cô lập) và hiệu quả. Thời gian chạy và stdlib chưa cung cấp bất kỳ cách tiếp cận tiêu chuẩn nào cho vấn đề này. Mô-đun stdlib trong tương lai sẽ giúp giảm thiểu nỗ lực duy trì sự cô lập và cung cấp các công cụ hiệu quả để giao tiếp (và chia sẻ) dữ liệu giữa các trình thông dịch.

Lỗi và cảnh báo

Vì các trình thông dịch phụ (và trình thông dịch chính) là một phần của cùng một quy trình nên khả năng cách ly giữa chúng không hoàn hảo --- ví dụ: sử dụng các thao tác tệp cấp thấp như os.close(), chúng có thể (vô tình hoặc cố ý) ảnh hưởng đến các tệp đang mở của nhau. Do cách chia sẻ tiện ích mở rộng giữa các trình thông dịch (phụ), một số tiện ích mở rộng có thể không hoạt động bình thường; điều này đặc biệt có thể xảy ra khi sử dụng các biến toàn cục khởi tạo một pha hoặc (tĩnh). Có thể chèn các đối tượng được tạo trong một trình thông dịch phụ vào vùng tên của một trình thông dịch (phụ) khác; điều này nên tránh nếu có thể.

Cần đặc biệt cẩn thận để tránh chia sẻ các hàm, phương thức, phiên bản hoặc lớp do người dùng xác định giữa các trình thông dịch phụ, vì các thao tác nhập được thực thi bởi các đối tượng đó có thể ảnh hưởng đến từ điển của trình thông dịch phụ (phụ) sai của các mô-đun đã tải. Điều quan trọng không kém là tránh chia sẻ các đối tượng mà từ đó có thể truy cập được những mục trên.

Cũng xin lưu ý rằng việc kết hợp chức năng này với các API PyGILState_* là rất khó, vì các API này giả định sự song song giữa trạng thái luồng Python và luồng cấp hệ điều hành, một giả định bị phá vỡ do sự hiện diện của trình thông dịch phụ. Chúng tôi khuyên bạn không nên chuyển đổi trình thông dịch phụ giữa một cặp lệnh gọi PyGILState_Ensure()PyGILState_Release() phù hợp. Hơn nữa, các tiện ích mở rộng (chẳng hạn như ctypes) sử dụng các API này để cho phép gọi mã Python từ các luồng không phải do Python tạo ra có thể sẽ bị hỏng khi sử dụng trình thông dịch phụ.

API cấp cao

type PyInterpreterState
Một phần của API có giới hạn (như một cấu trúc mờ đục).

Cấu trúc dữ liệu này thể hiện trạng thái được chia sẻ bởi một số luồng hợp tác. Các chủ đề thuộc cùng một trình thông dịch chia sẻ việc quản trị mô-đun của chúng và một số mục nội bộ khác. Không có thành viên công cộng trong cấu trúc này.

Các luồng thuộc về các trình thông dịch khác nhau ban đầu không chia sẻ gì, ngoại trừ trạng thái xử lý như bộ nhớ khả dụng, bộ mô tả tệp đang mở, v.v. Khóa trình thông dịch chung cũng được chia sẻ bởi tất cả các luồng, bất kể chúng thuộc về trình thông dịch nào.

Thay đổi trong phiên bản 3.12: PEP 684 đã giới thiệu khả năng của per-interpreter GIL. Xem Py_NewInterpreterFromConfig().

PyInterpreterState *PyInterpreterState_Get(void)
Một phần của ABI ổn định kể từ phiên bản 3.9.

Nhận thông dịch viên hiện tại.

Đưa ra lỗi nghiêm trọng nếu không có attached thread state. Nó không thể trả về NULL.

Added in version 3.9.

int64_t PyInterpreterState_GetID(PyInterpreterState *interp)
Một phần của ABI ổn định kể từ phiên bản 3.7.

Trả về ID duy nhất của thông dịch viên. Nếu có bất kỳ lỗi nào khi thực hiện việc này thì -1 sẽ được trả về và một lỗi sẽ được đặt ra.

Người gọi phải có attached thread state.

Added in version 3.7.

PyObject *PyInterpreterState_GetDict(PyInterpreterState *interp)
Giá trị trả về: Tham chiếu mượn. Một phần của ABI ổn định kể từ phiên bản 3.8.

Trả về một từ điển có thể lưu trữ dữ liệu dành riêng cho trình thông dịch. Nếu hàm này trả về NULL thì không có ngoại lệ nào được đưa ra và người gọi sẽ cho rằng không có lệnh dành riêng cho trình thông dịch nào.

Đây không phải là sự thay thế cho PyModule_GetState(), tiện ích mở rộng nào nên sử dụng để lưu trữ thông tin trạng thái dành riêng cho trình thông dịch.

Từ điển trả về được mượn từ trình thông dịch và có hiệu lực cho đến khi trình thông dịch tắt.

Added in version 3.8.

typedef PyObject *(*_PyFrameEvalFunction)(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)

Loại hàm đánh giá khung.

Tham số throwflag được sử dụng bởi phương pháp tạo throw(): nếu khác 0, hãy xử lý ngoại lệ hiện tại.

Thay đổi trong phiên bản 3.9: Hàm hiện có tham số tstate.

Thay đổi trong phiên bản 3.11: Tham số frame đã thay đổi từ PyFrameObject* thành _PyInterpreterFrame*.

_PyFrameEvalFunction _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp)

Nhận chức năng đánh giá khung.

Xem PEP 523 "Thêm đánh giá khung API vào CPython".

Added in version 3.9.

void _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame)

Đặt chức năng đánh giá khung.

Xem PEP 523 "Thêm đánh giá khung API vào CPython".

Added in version 3.9.

API cấp thấp

Tất cả các hàm sau phải được gọi sau Py_Initialize().

Thay đổi trong phiên bản 3.7: Py_Initialize() hiện khởi tạo GIL và đặt attached thread state.

PyInterpreterState *PyInterpreterState_New()
Một phần của ABI ổn định.

Tạo một đối tượng trạng thái trình thông dịch mới. attached thread state là không cần thiết, nhưng có thể tồn tại tùy chọn nếu cần tuần tự hóa các cuộc gọi đến chức năng này.

Tăng auditing event cpython.PyInterpreterState_New mà không có đối số.

void PyInterpreterState_Clear(PyInterpreterState *interp)
Một phần của ABI ổn định.

Đặt lại tất cả thông tin trong đối tượng trạng thái trình thông dịch. Phải có attached thread state cho trình thông dịch.

Tăng auditing event cpython.PyInterpreterState_Clear mà không có đối số.

void PyInterpreterState_Delete(PyInterpreterState *interp)
Một phần của ABI ổn định.

Phá hủy một đối tượng trạng thái thông dịch viên. Có should notattached thread state cho trình thông dịch đích. Trạng thái trình thông dịch phải được đặt lại bằng lệnh gọi trước tới PyInterpreterState_Clear().

Hỗ trợ trình gỡ lỗi nâng cao

Các chức năng này chỉ nhằm mục đích sử dụng bởi các công cụ gỡ lỗi nâng cao.

PyInterpreterState *PyInterpreterState_Head()

Trả về đối tượng trạng thái trình thông dịch ở đầu danh sách tất cả các đối tượng đó.

PyInterpreterState *PyInterpreterState_Main()

Trả về đối tượng trạng thái trình thông dịch chính.

PyInterpreterState *PyInterpreterState_Next(PyInterpreterState *interp)

Trả về đối tượng trạng thái trình thông dịch tiếp theo sau interp từ danh sách tất cả các đối tượng đó.

PyThreadState *PyInterpreterState_ThreadHead(PyInterpreterState *interp)

Trả về con trỏ tới đối tượng PyThreadState đầu tiên trong danh sách các luồng được liên kết với trình thông dịch interp.

PyThreadState *PyThreadState_Next(PyThreadState *tstate)

Trả về đối tượng trạng thái luồng tiếp theo sau tstate từ danh sách tất cả các đối tượng như vậy thuộc cùng một đối tượng PyInterpreterState.