42. Khóa nhập
42. Khóa nhập
Khóa nhập là cơ chế đồng bộ hóa giúp ngăn chặn việc nhập đồng thời không an toàn. Trong CPython, việc nhập không chỉ là tra cứu tên. Họ có thể tạo các đối tượng mô-đun, thay đổisys.modules, thực thi mã Python tùy ý, khởi tạo các mô-đun mở rộng, cập nhật thuộc tính gói, biên dịch tệp nguồn, đọc bộ đệm mã byte và chạy mã khởi tạo gói.
Nếu không khóa, hai luồng có thể nhập cùng một mô-đun cùng lúc và quan sát trạng thái mô-đun không nhất quán.
Khóa nhập tồn tại vì nhập là quá trình thực thi và quá trình thực thi sẽ thay đổi trạng thái thời gian chạy chung.
42.1 Tại sao hàng nhập khẩu cần khóa
Hãy xem xét mô-đun này:```python id="ez8v8a"
cache.py
print("initializing cache")
items = {}
def get(key):
return items[key]
Bây giờ hãy xem xét hai luồng thực hiện việc này cùng một lúc:python id="2pklaf"
import cache
Nếu không đồng bộ hóa, cả hai luồng có thể:text id="oy5qmp"
create a module object
insert or overwrite sys.modules["cache"]
execute cache.py
initialize items twice
observe a partially initialized module
bind different module objects
## 42.2 Nhập làm thay đổi trạng thái thời gian chạy toàn cầu
Nhập trạng thái chia sẻ chạm.
Các cấu trúc chia sẻ quan trọng bao gồm:```text id="yxk2bl"
sys.modules
sys.meta_path
sys.path
sys.path_hooks
sys.path_importer_cache
parent package attributes
module dictionaries
bytecode cache files
extension module state
```Điều quan trọng nhất là`sys.modules`.
```python id="ul20gj"
import sys
print(type(sys.modules))
sys.moduleslà một từ điển bình thường, nhưng nó là trung tâm của nhận dạng mô-đun. Nếu quá trình nhập hoặc thay thế mô-đun bị hỏng đồng thời, thời gian chạy có thể quan sát thấy các mô-đun trùng lặp hoặc các mô-đun không đầy đủ.
42.3 Mô hình tinh thần đơn giản
Việc nhập đơn giản có khóa trông như thế này:```python id="d8ycx5" def import_module(name): lock = get_import_lock_for(name)
with lock:
if name in sys.modules:
return sys.modules[name]
spec = find_spec(name)
module = module_from_spec(spec)
sys.modules[name] = module
try:
spec.loader.exec_module(module)
except Exception:
del sys.modules[name]
raise
return module
Việc triển khai thực tế có nhiều trường hợp hơn, nhưng ý tưởng trung tâm là:text id="dsb489"
for a given module name, only one thread should execute that module's initialization at a time
CPython hiện đại sử dụng khóa nhập dành riêng cho mô-đun trong máy nhập. Mục đích là để tránh tuần tự hóa tất cả các lần nhập không cần thiết trong khi vẫn ngăn hai luồng khởi tạo cùng một mô-đun cùng một lúc.
Về mặt khái niệm:```text id="r8nrgm"
import a locks module name "a"
import b locks module name "b"
import a again waits for "a" if another thread is initializing it
```Điều này mang lại nhiều tính đồng thời hơn so với một khóa nhập toàn cầu duy nhất, đồng thời duy trì sự an toàn cho từng danh tính mô-đun.
Khóa khóa là tên mô-đun đủ điều kiện:```text id="ev5p7h"
json
json.decoder
email.message
package.submodule
```Mỗi tên có ranh giới đồng bộ hóa nhập riêng.
## 42.5 Khóa nhập so với GIL
Khóa phiên dịch toàn cầu và khóa nhập giải quyết các vấn đề khác nhau.
| Cơ chế | Mục đích |
|---|---|
| GIL | Bảo vệ việc thực thi trình thông dịch và nhiều thao tác đối tượng nội bộ |
| Khóa nhập | Ngăn chặn việc khởi tạo mô-đun đồng thời không an toàn |
GIL không loại bỏ nhu cầu khóa nhập.
Quá trình nhập mô-đun có thể thực hiện I/O tệp, thực thi mã Python tùy ý, giải phóng GIL ở dạng mã gốc hoặc chờ các hoạt động khác. Một luồng khác có thể thử nhập tương tự trong khi quá trình nhập đầu tiên vẫn đang được tiến hành.
Khóa nhập bảo vệ bất biến cấp cao hơn:```text id="u3ib58"
one module name should not be initialized concurrently by multiple threads
```## 42.6 Nhập là được cấp lại
Nhập khẩu có thể kích hoạt nhập khẩu nhiều hơn.
Ví dụ:```python id="4hqnm0"
# app.py
import config
import server
# server.py
import logging
import socket
```Nhập khẩu`app`nhập khẩu`server`, và nhập khẩu`server`nhập các mô-đun khác.
Máy khóa nhập khẩu phải hỗ trợ nhập khẩu lồng nhau.
Về mặt khái niệm:```text id="k2va9e"
import app
lock app
execute app.py
import server
lock server
execute server.py
import socket
lock socket
execute socket.py
```Điều này là bình thường.
Một luồng đang nhập một mô-đun phải được phép nhập một mô-đun khác trong khi mô-đun đầu tiên vẫn đang khởi tạo.
## 42.7 Nhập đệ quy của cùng một mô-đun
Việc nhập đệ quy của cùng một mô-đun có thể xảy ra thông qua nhập vòng tròn.```python id="z153co"
# a.py
import b
x = 1
# b.py
import a
y = 2
```Khi`a`nhập khẩu`b`, Và`b`nhập khẩu`a`, lần nhập khẩu thứ hai của`a`không nên bế tắc chờ đợi cho chính nó.
Đây là một lý do khiến việc khóa nhập phải theo dõi quyền sở hữu và đệ quy một cách cẩn thận.
Hệ thống nhập xử lý việc này bằng cách trả về mô-đun được khởi tạo một phần từ`sys.modules`khi thích hợp.
Điều đó ngăn chặn đệ quy vô hạn và tự bế tắc, nhưng nó làm lộ ra trạng thái khởi tạo một phần.
## 42.8 Mô-đun được khởi tạo một phần
Trong quá trình nhập, CPython đặt mô-đun vào`sys.modules`trước khi thực thi mã của nó.
Điều này hỗ trợ nhập khẩu vòng tròn.
Vì:```python id="4jdauu"
# a.py
import b
value = 1
```Và:```python id="ayh1bo"
# b.py
import a
print(a.value)
```mô-đun`a`tồn tại ở`sys.modules`trước`value`được giao. Vì thế`b`có thể nhập khẩu`a`, Nhưng`a.value`có thể chưa tồn tại.
Khóa nhập ngăn chặn hai lần khởi tạo đồng thời. Nó không làm cho các mô-đun chưa hoàn thiện trở nên hoàn chỉnh.
Các vấn đề khác nhau:
| Vấn đề | Khóa nhập có giúp ích gì không? |
|---|---|
| Hai luồng khởi tạo cùng một mô-đun | Có |
| Nhập vòng quan sát mô-đun chưa hoàn chỉnh | Không |
| Đổ bóng mô-đun | Không |
| Xấu`sys.path`đặt hàng | Không |
| Tác dụng phụ cấp cao nhất | Chỉ ngăn chặn sự trùng lặp đồng thời |
Khóa cung cấp sự an toàn cho chủ đề. Nó không sửa cấu trúc phụ thuộc.
## 42.9 Lệnh mua lại khóa nhập
Nhập khẩu tạo thành chuỗi phụ thuộc. Một mô-đun có thể giữ khóa nhập của chính nó trong khi nhập mô-đun khác.
Ví dụ:```text id="m3czjg"
Thread 1:
lock a
import b
wait for lock b
Thread 2:
lock b
import a
wait for lock a
```Đây là một hình dạng bế tắc cổ điển.
Cơ chế nhập khẩu của CPython bao gồm tính năng phát hiện bế tắc xung quanh các khóa mô-đun. Khi phát hiện vòng chờ, nó có thể tránh bị treo vĩnh viễn và thay vào đó xử lý đường dẫn mô-đun được khởi tạo một phần.
Điểm thiết kế quan trọng là các khóa nhập phải xử lý các biểu đồ phụ thuộc vào lại và theo chu kỳ.
## 42.10 Ví dụ nhập theo luồng
Ví dụ này bắt đầu một số luồng nhập cùng một mô-đun.```python id="2bqtk9"
# slowmod.py
import time
print("slowmod start")
time.sleep(2)
value = 42
print("slowmod end")
# main.py
import threading
def worker():
import slowmod
print(slowmod.value)
threads = [threading.Thread(target=worker) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
```Hành vi dự kiến:```text id="mq93uj"
slowmod start
slowmod end
42
42
42
42
42
```Phần thân mô-đun thực thi một lần. Các luồng khác đợi quá trình nhập hoàn tất, sau đó sử dụng mô-đun được lưu trong bộ nhớ đệm.
## 42.11 Nhập đồng thời các mô-đun khác nhau
Khóa trên mỗi mô-đun cho phép các mô-đun khác nhau được nhập đồng thời khi các mô-đun phụ thuộc cho phép.
Chủ đề 1:```python id="jg44xg"
import alpha
```Chủ đề 2:```python id="vyvgaf"
import beta
```Nếu như`alpha`Và`beta`độc lập, việc nhập của chúng không cần phải chặn trên cùng một khóa mô-đun.
Tuy nhiên, họ vẫn có thể tranh cãi về:```text id="4h21u8"
the GIL
file-system operations
path importer caches
extension module initialization
shared package parents
custom finder state
```Có thể thực hiện nhập khẩu đồng thời, nhưng nhập khẩu vẫn là một hoạt động phức tạp trên toàn cầu.
## 42.12 Gói gốc và Khóa mô-đun con
Dành cho:```python id="bbvi06"
import package.submodule
```hệ thống nhập phải tải`package`Đầu tiên.
Về mặt khái niệm:```text id="9mmvrw"
lock package
initialize package
lock package.submodule
initialize package.submodule
bind package.submodule attribute
```Nếu một số chủ đề nhập các phần tử con khác nhau của cùng một gói:```python id="rp9s87"
import package.alpha
import package.beta
```họ có thể chia sẻ bước khởi tạo gói gốc.
Sau khi khởi tạo phần tử gốc, các phần tử con có thể được xử lý bằng các khóa mô-đun riêng của chúng, tùy theo hành vi của đường dẫn gói và công cụ tìm kiếm.
## 42.13 Khóa nhập khẩu toàn cầu trong`_imp`CPython hiển thị các chức năng khóa nhập cấp độ thấp thông qua`_imp`mô-đun.```python id="8j3c5t"
import _imp
print(_imp.lock_held())
```các`_imp`mô-đun bao gồm các chức năng như:```text id="iqvyee"
acquire_lock
release_lock
lock_held
```Đây là những giao diện thực hiện cấp thấp. Mã Python thông thường không nên sử dụng chúng để đồng bộ hóa ứng dụng.
Chúng tồn tại để nhập khẩu máy móc và khả năng tương thích.
Việc sử dụng chúng không đúng cách có thể làm bế tắc quá trình hoặc can thiệp vào hệ thống nhập.
## 42.14 Khóa nhập và Trình nhập tùy chỉnh
Trình tìm kiếm và trình tải tùy chỉnh phải cho rằng chúng có thể được gọi trong quá trình đồng bộ hóa nhập.
Người tìm kiếm nên tránh hành vi chậm, chặn hoặc quay lại khi có thể.
Một bộ nạp`exec_module`phương thức thực thi mã mô-đun. Nó có thể nhập các mô-đun khác.
Hình dạng bộ nạp ví dụ:```python id="viqfze"
class Loader:
def create_module(self, spec):
return None
def exec_module(self, module):
module.value = 42
```Nếu như`exec_module`nhập các mô-đun khác, nó tham gia vào cùng một biểu đồ khóa.
Các nhà nhập khẩu tùy chỉnh nên tránh mua các khóa không liên quan theo đơn đặt hàng không nhất quán. Nếu không, họ có thể tạo ra các bế tắc bên ngoài logic khóa nhập của CPython.
## 42.15 Khóa nhập và`sys.modules`Khóa bảo vệ phần quan trọng xung quanh việc khởi tạo mô-đun.
Các hoạt động quan trọng bao gồm:```text id="hu7f15"
checking sys.modules
creating the module
inserting the module
executing module code
removing module on failure
returning the initialized module
```Thời điểm nhạy cảm nhất là chèn trước khi thực hiện.```text id="fzh60s"
sys.modules[name] = module
exec_module(module)
```Thứ tự này hỗ trợ nhập vòng tròn, nhưng điều đó có nghĩa là mã khác có thể quan sát mô-đun trước khi quá trình thực thi kết thúc.
Khóa đảm bảo các luồng khác nhập cùng tên chờ hoàn thành thay vì thực hiện lại.
## 42.16 Lỗi nhập và nhả khóa
Nếu quá trình nhập không thành công, khóa phải được giải phóng.
Ví dụ:```python id="3sf7se"
# broken.py
raise RuntimeError("boom")
try:
import broken
except RuntimeError:
pass
```Sau khi thất bại, một chủ đề khác sẽ không bị chặn mãi mãi.
Hệ thống nhập khẩu phải:```text id="rhkz4v"
release the module lock
clean up sys.modules when appropriate
propagate the exception
allow future import attempts
```Lần nhập sau có thể thử lại:```python id="36ur7b"
import broken
```và sẽ thực thi lại mô-đun trừ khi một đối tượng mô-đun bị lỗi được máy móc đặc biệt cố tình để lại.
## 42.17 Mô-đun mở rộng gốc
Các mô-đun mở rộng làm phức tạp việc khóa nhập.
Một mô-đun mở rộng có thể:```text id="bc8bib"
run native initialization code
allocate process-global state
register types
import other modules
release the GIL
use static C variables
interact with subinterpreters
```Khóa nhập ngăn chặn việc khởi tạo đồng thời cùng một tên mô-đun mở rộng, nhưng tác giả tiện ích mở rộng vẫn cần viết mã khởi tạo một cách cẩn thận.
Khởi tạo nhiều pha hiện đại giúp các mô-đun mở rộng lưu trữ trạng thái trên mỗi đối tượng mô-đun thay vì chỉ trong toàn cục C.
## 42.18 Trình thông dịch phụ
Thông dịch viên phụ thêm một chiều hướng khác.
Mỗi trình thông dịch có trạng thái từ điển mô-đun riêng cho nhiều mô-đun, nhưng các mô-đun mở rộng gốc vẫn có thể có trạng thái C toàn quy trình trừ khi được thiết kế khác.
Khóa nhập phải được xem xét liên quan đến trạng thái trình thông dịch và trạng thái mở rộng.
Đối với các tác giả mở rộng, điều này có nghĩa là:```text id="l90pr8"
avoid mutable process-global module state
prefer per-module state
support multi-phase initialization
consider subinterpreter isolation
```Khóa nhập ngăn chặn các cuộc đua nhập đồng thời, nhưng nó không tự động làm cho trạng thái mô-đun mở rộng bị cô lập giữa các trình thông dịch.
## 42.19 Khóa nhập và tải lại`importlib.reload(module)`thực thi lại mã mô-đun.```python id="q3ox9f"
import importlib
import config
importlib.reload(config)
```Tải lại phải phối hợp với trạng thái nhập vì nó làm thay đổi từ điển mô-đun hiện có.
Trong quá trình tải lại, mã khác vẫn có thể chứa các tham chiếu đến:```text id="8a8ikh"
the module object
old functions
old classes
old constants
old imported names
```Khóa nhập có thể ngăn chặn các hoạt động tải lại/nhập xung đột đồng thời cho tên mô-đun, nhưng việc tải lại vẫn phức tạp về mặt ngữ nghĩa.
Tải lại không cập nhật tất cả các tài liệu tham khảo bên ngoài.
## 42.20 Khóa nhập và hiển thị trạng thái mô-đun
Khóa nhập kiểm soát các hoạt động nhập. Nó không làm cho mô-đun toàn cầu trở thành giao dịch.
Nếu mã mô-đun thay đổi trạng thái chung trong quá trình nhập:```python id="uwbmge"
registry = []
registry.append("phase 1")
registry.append("phase 2")
ready = True
```Mã liên quan đến nhập khẩu tuần hoàn có thể quan sát:```text id="spya66"
registry exists
registry contains only phase 1
ready does not exist yet
```Khóa nhập ngăn chặn việc thực thi trùng lặp đồng thời, nhưng các mô-đun được khởi tạo một phần vẫn hiển thị thông qua nhập vòng tròn.
## 42.21 Tránh các cuộc đua về thời gian nhập khẩu
Mã ứng dụng phải giảm thiểu đột biến thời gian nhập.
Thích điều này hơn:```python id="1kciks"
# service.py
class Service:
...
def create_service(config):
return Service(config)
```Về điều này:```python id="5wtu0a"
# service.py
config = load_config()
service = Service(config)
service.start()
```Biểu mẫu thứ hai thực hiện công việc tại thời điểm nhập. Khó kiểm tra hơn, khó tải lại hơn và nhạy cảm hơn với đơn hàng nhập khẩu.
Công việc trong thời gian nhập khẩu thường được giới hạn ở các định nghĩa và các hằng số rẻ tiền.
## 42.22 Mã cấp cao nhất an toàn
Mã mô-đun cấp cao nhất an toàn thường bao gồm:```text id="xbk1bu"
imports
constants
class definitions
function definitions
small table definitions
cheap feature detection
type aliases
```Mã cấp cao rủi ro hơn bao gồm:```text id="ippw6v"
network calls
database connections
thread startup
event loop startup
large file reads
global registration with side effects
process-wide configuration
monkey patching
```Khóa nhập tuần tự hóa quá trình khởi tạo, nhưng việc nhập đắt tiền hoặc có nhiều tác dụng phụ vẫn ảnh hưởng đến quá trình khởi động và hoạt động đồng thời.
## 42.23 Khóa nhập và nhập chết
"Nhập chết" là quá trình nhập chờ mô-đun không thể hoàn tất quá trình nhập do chu kỳ phụ thuộc hoặc khóa bên ngoài.
Ví dụ:```python id="jt6y2f"
# a.py
import threading
import b
lock = threading.Lock()
# b.py
import a
```Việc nhập vòng tròn đơn giản thường được xử lý bằng khả năng hiển thị mô-đun một phần. Nhưng nếu mã mô-đun nhận được các khóa bên ngoài, khởi động các luồng hoặc chờ các sự kiện trong quá trình nhập, thì việc tạo ra các khóa chết sẽ dễ dàng hơn nhiều.
Tránh chờ đợi trên các luồng hoặc khóa bên ngoài tại thời điểm nhập.
## 42.24 Khóa tùy chỉnh trong quá trình nhập
Đây là rủi ro:```python id="626g6o"
# registry.py
import threading
lock = threading.Lock()
with lock:
import plugin
```Nếu như`plugin`nhập khẩu`registry`và cố gắng lấy cùng một khóa, chương trình có thể bị bế tắc.
Thiết kế tốt hơn:```python id="9ez7pd"
# registry.py
import threading
lock = threading.Lock()
handlers = {}
def load_plugin(name):
import importlib
module = importlib.import_module(name)
return module
def register(name, handler):
with lock:
handlers[name] = handler
```Giữ các khóa ứng dụng bên ngoài chu kỳ phụ thuộc nhập nếu có thể.
## 42.25 Nhập hệ thống khóa và plugin
Hệ thống plugin thường nhập mô-đun một cách linh hoạt.```python id="iixpre"
import importlib
def load_plugins(names):
for name in names:
importlib.import_module(name)
```Nếu các plugin tự đăng ký tại thời điểm nhập, quá trình tải sẽ được tuần tự hóa theo tên mô-đun nhưng vẫn thay đổi các đăng ký dùng chung.
Thiết kế plugin an toàn hơn tách biệt việc nhập và đăng ký:```python id="d13tr6"
def load_plugin(name):
module = importlib.import_module(name)
return module.setup
```Sau đó gọi thiết lập trong giai đoạn được kiểm soát:```python id="p8oelv"
for setup in setups:
setup(registry)
```Điều này làm cho thứ tự khởi tạo trở nên rõ ràng.
## 42.26 Khóa nhập và hiệu suất khởi động
Khóa nhập có thể ảnh hưởng đến hiệu suất khởi động trong các chương trình theo luồng.
Nếu nhiều luồng bắt đầu và nhập các phần phụ thuộc một cách lười biếng, một số luồng có thể chặn trên cùng một lần nhập.
Một cải tiến chung là nhập các phần phụ thuộc chính trong quá trình khởi động đơn luồng:```python id="e6dmwr"
def main():
import logging
import database
import server
server.run()
```Việc khởi tạo tải trước này trước khi các luồng công việc bắt đầu.
Đối với các chương trình máy chủ, mẫu thông thường là:```text id="iq8ig9"
configure process
import dependencies
initialize application
start worker threads or event loop
```Tránh làm cho các luồng công nhân khám phá và nhập các biểu đồ phụ thuộc lớn một cách độc lập.
## 42.27 Chẩn đoán sự cố khóa nhập
Các triệu chứng của sự cố khóa nhập hoặc chu trình nhập bao gồm:```text id="m5z0iy"
program hangs during import
thread dump shows imports in multiple threads
partially initialized module errors
module attributes missing only during startup
plugin loading deadlocks
reload behaves inconsistently
```Các công cụ gỡ lỗi hữu ích:```python id="1mccj7"
import sys
print(sys.modules.get("module_name"))
import _imp
print(_imp.lock_held())
```Để bị treo, hãy sử dụng kết xuất luồng.```python id="csvfsw"
import faulthandler
faulthandler.dump_traceback()
```Hoặc kích hoạt trình xử lý lỗi từ dòng lệnh:```bash id="7wjpm2"
python -X faulthandler app.py
```## 42.28 Thời gian nhập và tranh chấp khóa
Thời gian nhập có thể được kiểm tra bằng:```bash id="gr9wta"
python -X importtime -c "import your_package"
```Báo cáo này báo cáo thời gian được sử dụng trong quá trình nhập.
Nó không trực tiếp hiển thị sự tranh chấp khóa, nhưng nó giúp xác định việc nhập chậm có thể trở thành điểm tranh chấp.
Nhập khẩu chậm thường đáng được chú ý khi chúng xảy ra:```text id="7c79oz"
inside worker startup
inside request paths
inside plugin discovery
inside command-line entry points
inside test collection
```## 42.29 Khóa nhập và mã không đồng bộ
Mã không đồng bộ không làm cho quá trình nhập không đồng bộ.
Một câu lệnh nhập khẩu bên trong một`async def`vẫn chạy đồng bộ khi phần coroutine đó thực thi.```python id="fg7lak"
async def handler():
import heavy_module
return heavy_module.run()
```Cuộc gọi đầu tiên đến`handler`đạt đến quá trình nhập có thể chặn vòng lặp sự kiện trong khi tải mô-đun.
Đối với các máy chủ không đồng bộ, hãy nhập các phần phụ thuộc nặng nề trước khi bắt đầu vòng lặp sự kiện hoặc di chuyển quá trình khởi tạo tốn kém vào các hook khởi động không đồng bộ rõ ràng.
## 42.30 Khóa nhập và đa xử lý
Mỗi quy trình có trạng thái trình thông dịch và trạng thái nhập riêng.
Với đa xử lý, các tiến trình con sẽ nhập các mô-đun riêng biệt.
Điều này quan trọng hơn trên các nền tảng hoặc các phương thức bắt đầu tạo ra các trình thông dịch mới.
Ví dụ: với việc tạo quy trình kiểu sinh sản, quy trình con sẽ nhập mô-đun chính.
Đây là lý do tại sao mã đa xử lý cần:```python id="w0j45j"
if __name__ == "__main__":
main()
```Nếu không có bộ bảo vệ, các tiến trình con có thể chạy lại mã cấp cao nhất trong quá trình nhập.
Khóa nhập chỉ bảo vệ nhập trong một quy trình. Nó không đồng bộ hóa việc nhập khẩu giữa các quy trình.
## 42.31 Khóa nhập và ghi bộ đệm bytecode
Khi CPython nhập mô-đun nguồn, nó có thể đọc hoặc ghi`.pyc`tập tin.
Mặt khác, việc nhập đồng thời có thể chạy đua xung quanh việc tạo bộ nhớ đệm mã byte.
Máy móc nhập khẩu xử lý việc này một cách cẩn thận. Tuy nhiên, bộ đệm mã byte là một sự tối ưu hóa chứ không phải là nguồn nhận dạng mô-đun.
Nguồn nhận dạng là:```text id="ka9zk4"
module name in sys.modules
```Bộ đệm mã byte chỉ lưu trữ các đối tượng mã đã biên dịch để nhập sau này nhanh hơn.
## 42.32 Khóa nhập và thay đổi hệ thống tập tin
Khóa nhập không làm cho trạng thái hệ thống tập tin ổn định.
Nếu các tệp được tạo, xóa hoặc thay thế trong khi quá trình nhập đang diễn ra, hành vi vẫn có thể gây nhầm lẫn.
Ví dụ:```text id="50enmw"
deployment replacing package files during process startup
tests generating modules dynamically
plugin files being written while discovery runs
zip archive changed while importing
```Sử dụng các phương pháp triển khai ổn định. Tránh thay đổi mã có thể nhập trong khi quá trình đang chạy đang nhập mã đó.
## 42.33 Khóa nhập không phải là khóa ứng dụng
Không sử dụng nhập làm cơ chế đồng bộ hóa.
Đây là một mô hình kém:```python id="xdy9zr"
def initialize_once():
import initialize_side_effects
```Nó dựa vào bộ nhớ đệm nhập để chạy khởi tạo một lần.
Thích khởi tạo một lần rõ ràng:```python id="q7vhsy"
_lock = threading.Lock()
_initialized = False
def initialize_once():
global _initialized
if _initialized:
return
with _lock:
if _initialized:
return
do_initialization()
_initialized = True
```Bộ nhớ đệm nhập dành cho các mô-đun. Vòng đời ứng dụng phải rõ ràng.
## 42.34 Quy tắc thiết kế: Nhập khẩu nên nhàm chán
Nhập khẩu an toàn nhất là nhàm chán.
Một nhập khẩu nhàm chán:```text id="tjyc8p"
defines names
sets constants
imports dependencies
does no external work
finishes quickly
```Một nhập khẩu đáng ngạc nhiên:```text id="8vcz4t"
starts threads
opens sockets
loads large models
registers global plugins
patches builtins
changes logging globally
reads mutable external config
```Khóa nhập khẩu có thể làm cho việc nhập khẩu đáng ngạc nhiên trở nên ít hấp dẫn hơn. Nó không thể làm cho họ dễ dàng lý luận.
## 42.35 Mô hình khóa nhập tối thiểu
Một mô hình nhỏ gọn:```text id="iwck6e"
Thread imports module M.
Import system acquires lock for M.
If M is already initialized, return it.
If M is initializing in another thread, wait.
If this thread is already involved in the cycle, use partial module state.
Create and cache M before execution.
Execute M.
On success, mark M initialized and release lock.
On failure, clean up and release lock.
```Mô hình này giải thích tại sao quá trình nhập đủ an toàn theo luồng để sử dụng thông thường, tại sao quá trình nhập tuần hoàn vẫn có thể hiển thị các mô-đun chưa hoàn thiện và tại sao tác dụng phụ của thời gian nhập vẫn nguy hiểm.
## 42,36 Điểm mấu chốt
Khóa nhập ngăn chặn việc khởi tạo đồng thời cùng tên mô-đun.
Khóa bảo vệ việc tải mô-đun chứ không phải ngữ nghĩa mô-đun tùy ý.
GIL và khóa nhập giải quyết các vấn đề khác nhau.
Quá trình nhập được thực hiện lại vì các mô-đun có thể nhập các mô-đun khác trong quá trình thực thi.
Việc nhập vòng tròn được xử lý bằng cách chèn các mô-đun vào`sys.modules`trước khi thực thi, điều này có thể làm lộ ra các mô-đun được khởi tạo một phần.
Các trình nhập tùy chỉnh, plugin, mô-đun mở rộng, trình thông dịch phụ, tải lại và khởi động theo luồng đều khiến việc khóa nhập trở nên quan trọng hơn.
Thiết kế ứng dụng tốt giúp nhập dữ liệu nhanh chóng, xác định và hầu như không có tác dụng phụ bên ngoài.