5. Mô hình thời gian chạy
5. Mô hình thời gian chạy
Thời gian chạy CPython là bộ máy tồn tại sau khi quá trình bắt đầu và trước khi mã Python kết thúc quá trình thực thi. Nó sở hữu trạng thái trình thông dịch, trạng thái luồng, mô-đun, nội dung, bộ cấp phát bộ nhớ, trạng thái ngoại lệ, trạng thái nhập, khung, cuộc gọi đang chờ xử lý, xử lý tín hiệu và hành vi tắt máy.
Một chương trình Python dường như chạy dưới dạng một chuỗi các câu lệnh. CPython chạy nó bên trong hệ thống thời gian chạy theo lớp.```text operating system process CPython runtime interpreter state thread state frame stack executing code object bytecode instructions object operations
## 5.1 Quy trình, Thời gian chạy, Trình thông dịch, Chủ đề, Khung
Một chương trình CPython đang chạy có một số đơn vị thực thi lồng nhau.
| Đơn vị | Ý nghĩa |
| ------------ | ----------------------------------------------------------------------------------- |
| Quy trình | Quá trình hệ điều hành chứa thời gian chạy nhúng hoặc thực thi CPython |
| Thời gian chạy | Trạng thái CPython toàn cầu được chia sẻ trong suốt quá trình |
| Thông dịch viên | Trạng thái trình thông dịch Python bị cô lập bên trong thời gian chạy |
| Trạng thái chủ đề | Trạng thái thực thi trên mỗi luồng cho một trình thông dịch |
| Khung | Một bối cảnh thực thi tích cực |
| Đối tượng mã | Mã byte và siêu dữ liệu được biên dịch |
| Đối tượng | Giá trị thời gian chạy được thao tác bằng mã byte |
Một kịch bản đơn giản:```python
x = 1
print(x + 2)
```chạy bên trong một khung. Khung đó thuộc về trạng thái luồng. Trạng thái luồng thuộc về một trình thông dịch. Trình thông dịch thuộc về thời gian chạy CPython.
Về mặt khái niệm:```text
_PyRuntimeState
PyInterpreterState
PyThreadState
PyFrameObject or internal frame
PyCodeObject
locals
globals
builtins
value stack
```Cấu trúc C chính xác thay đổi giữa các phiên bản, nhưng hệ thống phân cấp đủ ổn định để hướng dẫn việc đọc nguồn.
## 5.2 Thời gian chạy
Thời gian chạy là trạng thái CPython trên toàn quy trình.
Nó bao gồm các dịch vụ toàn cầu được sử dụng bởi một hoặc nhiều phiên dịch viên:```text
runtime initialization state
memory allocator state
interpreter list
GIL state
pending calls
signal handling state
audit hook state
global caches
runtime finalization flags
```Ở mức độ cao, thời gian chạy trả lời:```text
Has CPython been initialized?
Which interpreters exist?
Is the runtime finalizing?
Which global locks and services are active?
Which process-wide hooks are installed?
```Điều này quan trọng nhất trong quá trình khởi động, tắt máy, nhúng, trình thông dịch phụ và hoạt động phân luồng tự do.
Một tập lệnh Python bình thường thường có một thời gian chạy và một trình thông dịch chính.
## 5.3 Trạng thái thông dịch viên
Trạng thái trình thông dịch đại diện cho một trình thông dịch Python bên trong quy trình.
Nó sở hữu trạng thái cấp độ ngôn ngữ như:```text
modules dictionary
builtins
import state
sys module state
codec state
warnings state
garbage collector state
interned strings
per-interpreter caches
execution configuration
```Về mặt khái niệm:```text
PyInterpreterState
modules
builtins
sysdict
import machinery
gc generations
codec registry
pending async exception state
```Hầu hết các chương trình Python chỉ sử dụng trình thông dịch chính.
Trình thông dịch phụ tạo các trạng thái thông dịch viên bổ sung trong cùng một quy trình. Chúng có thể có các từ điển mô-đun và trạng thái thời gian chạy riêng biệt, nhưng chúng vẫn chia sẻ một số tài nguyên ở cấp quy trình.
## 5.4 Trạng thái chủ đề
Mỗi luồng hệ điều hành thực thi mã Python cần có trạng thái luồng.
Trạng thái luồng lưu trữ thông tin thực thi cho một luồng trong một trình thông dịch:```text
current frame
current exception state
recursion depth
profiling function
tracing function
async exception request
thread-local interpreter data
```Trạng thái luồng kết nối thực thi gốc với thực thi Python.
Về mặt khái niệm:```text
current OS thread
PyThreadState
current interpreter
current frame
exception information
tracing and profiling hooks
```Khi mã C cần đưa ra một ngoại lệ, truy cập khung hiện tại hoặc tương tác với API Python, nó thường cần trạng thái luồng hiện tại.
## 5,5 khung hình
Một khung là một bản ghi thực thi.
CPython tạo một khung khi nó thực thi phần thân mô-đun, phần thân hàm, phần thân lớp, trình tạo, coroutine hoặc phần hiểu.
Một khung chứa:```text
code object
globals dictionary
builtins dictionary
locals storage
value stack
instruction pointer
block and exception state
line number state
owner information
```Đối với một chức năng:```python
def add(a, b):
c = a + b
return c
```đang gọi điện`add(2, 3)`tạo khung cho cuộc gọi đó.
Các cửa hàng khung:```text
code object for add
a = 2
b = 3
c after assignment
temporary stack values
current bytecode offset
globals of the defining module
builtins visible to the function
```Khung là đối tượng thời gian chạy cụ thể giúp thực hiện lệnh gọi hàm.
## 5.6 Đối tượng mã
Một đối tượng mã được biên dịch nội dung thực thi.
Đó là siêu dữ liệu bất biến cộng với mã byte. Nó không lưu trữ các giá trị hiện tại của các biến cục bộ.
Ví dụ:```python
def f(x):
y = x + 1
return y
```Đối tượng hàm có một đối tượng mã:```python
code = f.__code__
print(code.co_name)
print(code.co_varnames)
print(code.co_consts)
```Đối tượng mã cho biết cách thực thi hàm. Khung lưu trữ một lần thực thi đang hoạt động của mã đó.
Sự khác biệt này quan trọng:```text
code object: reusable compiled program
frame: one running invocation of that program
function: callable object that wraps code with globals, defaults, and closure
```Nhiều lệnh gọi đến cùng một hàm sử dụng lại cùng một đối tượng mã nhưng tạo các khung riêng biệt.
## 5.7 Đối tượng hàm
Đối tượng hàm Python bao bọc một đối tượng mã bằng ngữ cảnh thời gian chạy.
Nó chứa:```text
code object
globals dictionary
defaults
keyword-only defaults
closure cells
annotations
function name
qualified name
module name
dict for custom attributes
```Ví dụ:```python
x = 10
def f(y):
return x + y
```chức năng`f`cần nhiều hơn mã byte. Nó cũng cần từ điển toàn cầu nơi`x`có thể được tìm thấy.
Về mặt khái niệm:```text
PyFunctionObject
func_code code for f
func_globals module globals
func_defaults default argument values
func_closure captured cells
```Việc gọi hàm sẽ tạo một khung bằng cách sử dụng các thành phần này.
## 5.8 Đối tượng và kiểu
Khi chạy, mã byte xử lý các tham chiếu đối tượng.
Mọi giá trị Python đều là một đối tượng:```python
42
"hello"
[1, 2, 3]
{"a": 1}
lambda x: x + 1
Exception("bad")
```Mỗi đối tượng có một loại.
Loại xác định hành vi:```text
allocation
deallocation
attribute lookup
method lookup
call behavior
numeric operations
sequence operations
mapping operations
iteration
representation
hashing
comparison
```Ví dụ:```python
len(x)
```không trực tiếp kiểm tra mọi cách bố trí đối tượng có thể. Nó hỏi loại đối tượng có độ dài hoạt động như thế nào.
Đây là lý do tại sao mô hình thời gian chạy phụ thuộc vào loại đối tượng. Hướng dẫn mã byte là chung chung. Các khe loại cung cấp hành vi cụ thể.
## 5.9 Không gian tên
Việc thực thi Python sử dụng không gian tên.
Các loại không gian tên chính là:
| Không gian tên | Sao lưu lưu trữ |
| ----------------- | ------------------------------------------------- |
| Người dân địa phương | Chức năng cục bộ nhanh, không gian tên lớp hoặc ánh xạ |
| Toàn cầu | Từ điển mô-đun |
| Nội dung | Từ điển dựng sẵn |
| Thuộc tính đối tượng | Lệnh chính tả, vị trí, bộ mô tả, tra cứu kiểu |
| Thuộc tính mô-đun | Từ điển mô-đun |
Đối với mã này:```python
x = 1
def f():
return x + len([1, 2])
```bên trong`f`, CPython giải quyết:```text
x global name in module dictionary
len builtin name if not found in globals
[1,2] newly created list object
```Độ phân giải tên phụ thuộc vào phân loại thời gian biên dịch và tra cứu không gian tên thời gian chạy.
## 5.10 Người dân địa phương nhanh
Các biến cục bộ của hàm thường được lưu trữ theo bố cục giống như mảng được tối ưu hóa, không phải tra cứu từ điển thông thường cho mỗi lần truy cập.
Đối với một chức năng:```python
def f(a, b):
c = a + b
return c
```trình biên dịch biết tên địa phương:```text
a
b
c
```Khung có thể lưu trữ chúng trong các khe được lập chỉ mục.
Bytecode sau đó có thể sử dụng các hoạt động cục bộ nhanh:```text
LOAD_FAST a
LOAD_FAST b
STORE_FAST c
```Việc này nhanh hơn tra cứu từ điển.
Từ điển địa phương có thể được cụ thể hóa khi cần thiết, ví dụ bằng cách`locals()`, truy tìm, gỡ lỗi hoặc kiểm tra khung. Nhưng đường dẫn thực thi thông thường sử dụng cục bộ nhanh.
## 5.11 Ngăn xếp giá trị
Mã byte CPython sử dụng máy xếp chồng.
Hướng dẫn đẩy và bật các giá trị tạm thời từ ngăn xếp giá trị của khung.
Vì:```python
z = x + y
```việc thực hiện khái niệm là:```text
LOAD_FAST x push x
LOAD_FAST y push y
BINARY_OP + pop x and y, push result
STORE_FAST z pop result into local z
```Ngăn xếp giá trị là nơi lưu trữ thực thi tạm thời. Nó tách biệt với các biến cục bộ.
Do đó, một khung chứa cả hai:```text
local variable storage
value stack for intermediate operations
```Điều này giải thích tại sao mã byte có thể nhỏ gọn. Hướng dẫn giao tiếp thông qua ngăn xếp.
## 5.12 Cuộc gọi
Lệnh gọi hàm là một trong những hoạt động thời gian chạy quan trọng nhất.
Vì:```python
result = f(1, 2)
```CPython phải:```text
evaluate f
evaluate arguments
prepare call layout
check callable type
create or enter callable execution
bind arguments
execute body or C function
return result
```Các đối tượng có thể gọi khác nhau có đường dẫn khác nhau:
| Có thể gọi được | Đường dẫn thời gian chạy |
| ---------------------- | ------------------------------------------ |
| Hàm Python | Tạo khung và thực thi đối tượng mã |
| Chức năng tích hợp | Gọi trình bao bọc hàm C |
| Phương pháp | Liên kết người nhận và gọi hàm cơ bản |
| Lớp | Phân bổ và khởi tạo phiên bản |
| Đối tượng với`__call__`| Gọi loại khe gọi |
CPython đã tối ưu hóa các quy ước gọi để giảm việc phân bổ từ điển và bộ dữ liệu tạm thời. CPython hiện đại sử dụng các đường dẫn cuộc gọi nhanh như vectorcall cho nhiều loại có thể gọi được.
## 5.13 Ngoại lệ
Ngoại lệ là trạng thái thời gian chạy cộng với luồng điều khiển.
Khi mã Python tăng:```python
raise ValueError("bad")
```CPython ghi lại thông tin ngoại lệ ở trạng thái luồng hiện tại và bắt đầu thực hiện tháo gỡ.
Hàm C thường báo lỗi bằng cách:```text
setting an exception
returning NULL or -1
```Người gọi kiểm tra giá trị trả về và truyền bá lỗi.
Về mặt khái niệm:```c
PyErr_SetString(PyExc_ValueError, "bad");
return NULL;
```Ở cấp độ bytecode, các ngoại lệ ảnh hưởng đến:```text
current frame
exception table
stack unwinding
finally blocks
except matching
traceback construction
propagation to caller
```Các ngoại lệ không phải là giá trị trả về thông thường. Chúng là một đường dẫn điều khiển riêng biệt trong suốt thời gian chạy.
## 5.14 Truy nguyên
Truy nguyên ghi lại nơi ngoại lệ đã di chuyển.
Khi một ngoại lệ lan truyền qua các khung, CPython có thể đính kèm các mục truy nguyên xác định:```text
file name
function name
line number
bytecode position
frame
```Ví dụ:```python
def a():
b()
def b():
1 / 0
a()
```Truy nguyên chứa chuỗi cuộc gọi:```text
module frame
a frame
b frame
```Dấu vết là đối tượng. Việc giữ dấu vết có thể giữ cho khung hình tồn tại. Giữ khung có thể giữ cho các biến cục bộ tồn tại. Điều này rất quan trọng đối với hành vi bộ nhớ.
## 5.15 Mô-đun
Mô-đun là một đối tượng có không gian tên.
Khi CPython nhập một mô-đun, nó sẽ tạo hoặc truy xuất một đối tượng mô-đun và thực thi mã trong từ điển của mô-đun đó.
Về mặt khái niệm:```text
find module spec
create module object
insert into sys.modules
execute module code in module namespace
return module object
```Từ điển mô-đun trở thành không gian tên chung cho các hàm được xác định trong mô-đun đó.
Vì:```python
# example.py
x = 10
def f():
return x
```chức năng`f`lưu trữ một tham chiếu đến từ điển toàn cầu của mô-đun. Khi`f`nhìn lên`x`, nó sẽ tìm kiếm từ điển đó.
## 5.16`sys.modules`
`sys.modules`là bộ đệm nhập.
Nó ánh xạ tên mô-đun tới các đối tượng mô-đun.```python
import sys
print(sys.modules["sys"])
```Bộ đệm này ngăn việc nhập lặp lại thực thi lại cùng một mô-đun.
Nhập cùng một mô-đun hai lần thường trả về mô-đun đã được tải:```python
import math
import math
```Kiểm tra nhập khẩu thứ hai`sys.modules`và tái sử dụng mô-đun.
Bộ đệm này cũng xử lý việc nhập vòng tròn. Một mô-đun có thể xuất hiện trong`sys.modules`trước khi mã của nó thực thi xong.
## 5.17 Nội dung
Nội dung là tên có sẵn khi tra cứu cục bộ và toàn cầu không thành công.
Ví dụ:```python
len
print
range
object
type
Exception
```Một khung có quyền truy cập vào một từ điển dựng sẵn.
Tra cứu tên cho một tên không đủ tiêu chuẩn bên trong một hàm đại khái như sau:```text
locals
globals
builtins
```Vì thế:```python
def f(xs):
return len(xs)
```thường giải quyết`len`từ nội dung trừ khi có tên toàn cầu`len`phủ bóng lên nó.
Đường dẫn tra cứu này là một phần của mô hình thời gian chạy. Nó giải thích tại sao việc gán một tên toàn cục`len`thay đổi hành vi bên trong mô-đun.
## 5.18 Bộ mô tả và quyền truy cập thuộc tính
Quyền truy cập thuộc tính là công văn thời gian chạy.
Vì:```python
obj.name
```CPython không chỉ nhìn vào bên trong`obj.__dict__`.
Nó tuân theo các quy tắc tra cứu mô tả và kiểu:```text
look on type for data descriptor
look in instance dictionary
look on type for non-data descriptor or class attribute
call __getattr__ if needed
raise AttributeError if missing
```Đây là lý do tại sao các phương thức liên kết tự động:```python
class C:
def f(self):
return 1
c = C()
m = c.f
```Đối tượng hàm được lưu trữ trên lớp là một bộ mô tả. Việc truy cập nó thông qua một phiên bản sẽ tạo ra một phương thức bị ràng buộc hoặc một đường dẫn cuộc gọi tương đương được tối ưu hóa.
Tra cứu thuộc tính kết nối bố cục đối tượng, loại đối tượng, bộ mô tả, lệnh gọi phương thức và hiệu suất.
## 5.19 Lặp lại
Lặp lại sử dụng một giao thức thời gian chạy nhỏ.
Vì:```python
for x in obj:
body(x)
```CPython thực hiện đại khái:```text
iterator = iter(obj)
loop:
x = next(iterator)
if StopIteration: exit loop
execute body
```Ở cấp độ C, điều này ánh xạ tới các vị trí loại và trình trợ giúp giao thức.
Đối tượng iterator lưu trữ trạng thái lặp. Đối với trình vòng lặp danh sách, trạng thái đó bao gồm danh sách và chỉ mục hiện tại. Đối với một trình tạo, trình vòng lặp chính là khung thực thi bị treo.
Giao thức chung này có quyền hạn:```text
for loops
comprehensions
tuple unpacking
list()
sum()
any()
all()
many standard library functions
```## 5.20 Trình tạo và Coroutine
Máy phát điện là một khung treo.
Vì:```python
def gen():
yield 1
yield 2
```đang gọi điện`gen()`không thực hiện ngay cơ thể. Nó tạo ra một đối tượng máy phát điện.
Trình tạo sở hữu trạng thái thực thi:```text
code object
suspended frame or frame-like state
instruction position
local variables
exception state
running flag
```Đang gọi`next()`tiếp tục thực hiện cho đến lần tiếp theo`yield`hoặc quay trở lại.
Coroutine và trình tạo async mở rộng ý tưởng này bằng hành vi giao thức có thể chờ đợi và tích hợp vòng lặp sự kiện.
## 5.21 Quản lý bộ nhớ
Thời gian chạy quản lý bộ nhớ ở nhiều lớp.```text
raw memory allocator
object allocator
type-specific free lists or caches
reference counting
cyclic garbage collector
```Việc đếm tham chiếu xử lý hầu hết các sự kiện trong đời:```text
new reference increases lifetime
decref releases ownership
zero refcount triggers deallocation
```Trình thu gom rác tuần hoàn xử lý các chu kỳ đối tượng không thể truy cập được.
Quản lý bộ nhớ là một phần của hành vi thời gian chạy vì việc hủy đối tượng có thể thực thi mã thông qua bộ hoàn thiện, lệnh gọi lại điểm yếu hoặc đường dẫn giải phóng để giải phóng nhiều đối tượng hơn.
## 5.22 GIL trong Mô hình thời gian chạy
Trong thời gian chạy CPython truyền thống, Khóa phiên dịch toàn cầu bảo vệ việc thực thi mã byte Python và nhiều cấu trúc dữ liệu nội bộ.
GIL đơn giản hóa:```text
reference count updates
object mutation invariants
interpreter state access
C extension assumptions
```Một luồng phải giữ GIL để thực thi mã byte Python.
Các tiện ích mở rộng C có thể giải phóng GIL xung quanh việc chặn hoặc tác phẩm gốc chạy lâu dài, sau đó lấy lại GIL trước khi chạm lại vào các đối tượng Python.
Do đó, mô hình thời gian chạy có hai lớp đồng thời:```text
OS threads may run concurrently
Python bytecode execution is serialized by the GIL in traditional builds
```CPython hiện đại cũng có công việc xây dựng theo luồng tự do, điều này làm thay đổi nhiều giả định nội bộ. Nhưng mô hình GIL truyền thống vẫn cần thiết để hiểu mã và phần mở rộng hiện có.
## 5.23 Khởi tạo
Trước khi mã Python chạy, CPython khởi tạo thời gian chạy.
Khởi động bao gồm:```text
configure memory allocators
initialize runtime state
create main interpreter
create main thread state
initialize builtins
initialize sys
set up import machinery
initialize encodings
configure paths
process command-line options
run startup hooks
execute requested code
```Đây là lý do tại sao mã khởi động lại phức tạp. Nhiều mô-đun phụ thuộc vào các mô-đun khác đã tồn tại nhưng bản thân hệ thống nhập cũng cần hỗ trợ thời gian chạy.
CPython tự khởi động cẩn thận.
## 5.24 Tắt máy
Tắt máy cũng phức tạp.
Việc hoàn thiện có thể bao gồm:```text
running atexit handlers
flushing standard streams
clearing modules
destroying interpreter state
collecting garbage
finalizing objects
releasing memory
tearing down runtime services
```Các đối tượng có thể chạy trình hoàn thiện trong khi tắt máy, nhưng toàn cầu mô-đun của chúng có thể đã bị xóa. Đây là lý do tại sao lỗi tắt máy có thể rất tinh vi.
Một công cụ hoàn thiện mong đợi`sys`, `os`hoặc một mô-đun khác có sẵn đầy đủ có thể bị lỗi trong quá trình phân tích trình thông dịch.
## 5.25 Python nhúng
CPython có thể được nhúng bên trong một chương trình C hoặc C++ khác.
Trong trường hợp đó, quy trình máy chủ sẽ kiểm soát việc khởi tạo và hoàn thiện thời gian chạy.
Về mặt khái niệm:```c
Py_Initialize();
/* run Python code */
Py_Finalize();
```Việc nhúng làm cho hệ thống phân cấp thời gian chạy hiển thị rõ ràng hơn. Thời gian chạy CPython tồn tại bên trong một quy trình có thể có các luồng, bộ cấp phát, vòng lặp sự kiện, hệ thống ghi nhật ký và quy tắc tắt máy riêng.
Mã nhúng phải tôn trọng:```text
initialization order
thread state management
GIL rules
reference ownership
exception handling
finalization constraints
```## 5.26 Trạng thái thời gian chạy và khả năng quan sát
Python hiển thị các phần của thời gian chạy thông qua các mô-đun tiêu chuẩn.
| Mô-đun | Xem thời gian chạy |
| ------------- | ------------------------------------------------------ |
|`sys`| Cài đặt thông dịch, mô-đun, đường dẫn, khung, số tiền hoàn lại |
|`gc`| Nhà nước thu gom rác |
|`inspect`| Khung, chức năng, nguồn, chữ ký |
|`dis`| Mã byte |
|`types`| Đối tượng loại thời gian chạy |
|`threading`| Tóm tắt luồng Python |
|`tracemalloc`| Dấu vết phân bổ |
|`importlib`| Nhập khẩu máy móc |
Ví dụ:```python
import sys
import gc
import inspect
print(sys.modules.keys())
print(gc.get_count())
print(inspect.currentframe())
```Các mô-đun này rất hữu ích vì chúng cho phép bạn quan sát cấu trúc thời gian chạy mà không cần đọc mã C ngay lập tức.
## 5.27 Bản phác thảo thực thi hoàn chỉnh
Đối với chương trình này:```python
def add(a, b):
return a + b
print(add(2, 3))
```CPython đại khái thực hiện:```text
initialize runtime
create main interpreter
create main thread state
load builtins and sys
compile module source to code object
create module frame
execute module bytecode
define function:
create code object for add
create function object
bind name add in module globals
call print(add(2, 3)):
load print from builtins
load add from globals
load constants 2 and 3
call add
create frame for add
bind a = 2, b = 3
load a
load b
perform binary addition through object protocol
return integer result
call print
execute built-in C function
write output
discard return value
finish module frame
run shutdown sequence
```Đây là mô hình thời gian chạy đang hoạt động.
## 5.28 Mô hình tinh thần làm việc
Giữ mô hình nhỏ gọn này trong khi đọc các chương sau:```text
A process owns a CPython runtime.
The runtime owns interpreters.
An interpreter owns modules, builtins, import state, and GC state.
A thread state owns the current execution state for one thread.
A frame runs one code object.
Bytecode instructions operate on a value stack and local storage.
Objects carry type pointers.
Types define behavior through slots.
Errors use exception state plus sentinel returns.
Memory is managed by reference counting plus cyclic GC.
```Mô hình này kết nối hầu hết các bộ phận bên trong CPython.
## 5.29 Tóm tắt chương
Thời gian chạy CPython là một hệ thống thực thi theo lớp. Quá trình này chứa một thời gian chạy. Thời gian chạy chứa các trạng thái thông dịch viên. Mỗi luồng thực thi có một trạng thái luồng. Mỗi cuộc gọi đang hoạt động đều có một khung. Mỗi khung chạy một đối tượng mã. Bytecode thao tác các tham chiếu đối tượng. Các đối tượng trỏ đến các loại. Các loại xác định hành vi.
Hiểu được hệ thống phân cấp này làm cho phần còn lại của CPython dễ đọc hơn. Khởi động, nhập, gọi hàm, ngoại lệ, tạo, thu gom rác và tắt máy đều phù hợp với cùng một mô hình thời gian chạy.