Tại sao lại khóa thông dịch viên toàn cầu?


89

Chức năng chính xác của Khóa thông dịch viên toàn cầu của Python là gì? Các ngôn ngữ khác được biên dịch sang bytecode có sử dụng cơ chế tương tự không?


6
Bạn cũng nên hỏi "Liệu nó có quan trọng không?"
S.Lott

2
Tôi đồng ý, tôi coi đây là một vấn đề không thành vấn đề khi mà trong phiên bản 2.6, mô-đun đa xử lý đã được thêm vào để cho phép bạn lập trình bằng nhiều quy trình theo cách giống như một luồng. docs.python.org/library/multiprocessing.html
Monut

Câu trả lời:


69

Nói chung, đối với bất kỳ vấn đề an toàn luồng nào, bạn sẽ cần phải bảo vệ cấu trúc dữ liệu bên trong của mình bằng khóa. Điều này có thể được thực hiện với nhiều mức độ chi tiết khác nhau.

  • Bạn có thể sử dụng khóa hạt mịn, trong đó mọi cấu trúc riêng biệt đều có khóa riêng.

  • Bạn có thể sử dụng khóa chi tiết thô trong đó một khóa bảo vệ mọi thứ (phương pháp GIL).

Có những ưu và nhược điểm khác nhau của mỗi phương pháp. Khóa chi tiết cho phép khả năng song song lớn hơn - hai luồng có thể thực thi song song khi chúng không chia sẻ bất kỳ tài nguyên nào. Tuy nhiên, có một chi phí quản lý lớn hơn nhiều. Đối với mỗi dòng mã, bạn có thể cần lấy và phát hành một số khóa.

Cách tiếp cận hạt thô thì ngược lại. Hai luồng không thể chạy cùng một lúc, nhưng một luồng riêng lẻ sẽ chạy nhanh hơn vì nó không thực hiện quá nhiều việc ghi sổ. Cuối cùng, nó đi đến sự cân bằng giữa tốc độ đơn luồng và song song.

Đã có một vài nỗ lực để loại bỏ GIL trong python, nhưng chi phí bổ sung cho các máy luồng đơn thường quá lớn. Một số trường hợp thực sự có thể chậm hơn ngay cả trên các máy nhiều bộ xử lý do tranh chấp khóa.

Các ngôn ngữ khác được biên dịch sang bytecode có sử dụng cơ chế tương tự không?

Nó khác nhau và có lẽ nó không nên được coi là một thuộc tính ngôn ngữ quá nhiều như một thuộc tính triển khai. Ví dụ: có các triển khai Python như Jython và IronPython sử dụng phương pháp phân luồng của máy ảo cơ bản của chúng, thay vì phương pháp GIL. Ngoài ra, phiên bản tiếp theo của Ruby có vẻ sẽ hướng tới việc giới thiệu GIL.


1
bạn có thể giải thích điều này: 'Hai luồng không thể chạy cùng một lúc'? Gần đây tôi đã viết một máy chủ web đơn giản bằng Python với đa luồng. Đối với mỗi yêu cầu mới từ máy khách, các máy chủ tạo ra một luồng mới cho nó và các luồng đó tiếp tục thực thi. Như vậy sẽ có nhiều luồng chạy cùng lúc đúng không? Hay tôi đã hiểu sai?
avi

1
@avi AFAIK các luồng python không thể chạy đồng thời, nhưng điều đó không có nghĩa là một luồng phải chặn luồng kia. GIL chỉ có nghĩa là chỉ một luồng có thể diễn giải mã python tại một thời điểm, điều đó không có nghĩa là quản lý luồng và phân bổ tài nguyên không hoạt động.
Benproductions1

2
^ vì vậy tại bất kỳ thời điểm nào, sẽ chỉ có một luồng đang phân phát nội dung cho máy khách ... vì vậy không nên thực sự sử dụng đa luồng để cải thiện hiệu suất. đúng?
avi

Và, tất nhiên, Java được biên dịch thành mã byte và cho phép khóa chi tiết rất tốt.
Warren Dew

3
@avi, một quy trình liên kết IO như máy chủ web vẫn có thể thu được từ các luồng Python. Hai hoặc nhiều luồng có thể thực hiện IO đồng thời. Chúng không thể được giải thích (CPU) đồng thời.
Saish

33

Sau đây là từ Hướng dẫn tham khảo API Python / C chính thức :

Trình thông dịch Python không hoàn toàn an toàn cho chuỗi. Để hỗ trợ các chương trình Python đa luồng, có một khóa toàn cầu phải được giữ bởi luồng hiện tại trước khi nó có thể truy cập các đối tượng Python một cách an toàn. Nếu không có khóa, ngay cả những thao tác đơn giản nhất cũng có thể gây ra sự cố trong một chương trình đa luồng: ví dụ: khi hai luồng đồng thời tăng số lượng tham chiếu của cùng một đối tượng, số lượng tham chiếu có thể chỉ được tăng một lần thay vì hai lần.

Do đó, quy tắc tồn tại rằng chỉ luồng đã có được khóa thông dịch toàn cục mới có thể hoạt động trên các đối tượng Python hoặc gọi các hàm API Python / C. Để hỗ trợ các chương trình Python đa luồng, trình thông dịch thường xuyên phát hành và yêu cầu lại khóa - theo mặc định, cứ mỗi 100 lệnh bytecode (điều này có thể được thay đổi bằng sys.setcheckinterval ()). Khóa cũng được phát hành và được yêu cầu lại xung quanh khả năng chặn các hoạt động I / O như đọc hoặc ghi tệp, để các luồng khác có thể chạy trong khi luồng yêu cầu I / O đang đợi hoạt động I / O hoàn tất.

Tôi nghĩ nó tóm tắt vấn đề khá tốt.


1
Tôi đọc nó quá, nhưng tôi không thể hiểu tại sao Python là khác nhau trong lĩnh vực này từ, nói, java (is it?)
Federico A. Ramponi

@EliBendersky Python đề được thực hiện như pthreads và được xử lý bởi hệ điều hành ( dabeaz.com/python/UnderstandingGIL.pdf ) trong khi Java đề là mức độ xử lí ứng dụng whos lịch được xử lý bởi JVM
gokul_uf

19

Khóa thông dịch viên toàn cục là một khóa kiểu mutex lớn để bảo vệ các bộ đếm tham chiếu không bị hosed. Nếu bạn đang viết mã python thuần túy, tất cả điều này xảy ra đằng sau hậu trường, nhưng nếu bạn nhúng Python vào C, thì bạn có thể phải lấy / giải phóng khóa một cách rõ ràng.

Cơ chế này không liên quan đến việc Python được biên dịch thành bytecode. Nó không cần thiết cho Java. Trên thực tế, nó thậm chí không cần thiết cho Jython (python được biên dịch sang jvm).

xem thêm câu hỏi này


4
"Cơ chế này không liên quan đến việc Python được biên dịch thành bytecode": Chính xác, đó là một tạo tác của việc triển khai CPython. Các triển khai khác (như Jython mà bạn đã đề cập) có thể không bị hạn chế này nhờ triển khai an toàn theo chuỗi của chúng
Eli Bendersky

11

Python, giống như perl 5, không được thiết kế từ đầu để an toàn cho luồng. Các luồng được ghép vào sau thực tế, do đó, khóa thông dịch chung được sử dụng để duy trì loại trừ lẫn nhau ở nơi chỉ có một luồng đang thực thi mã tại một thời điểm nhất định trong ruột của trình thông dịch.

Các luồng Python riêng lẻ được chính trình thông dịch xử lý đa nhiệm một cách hợp tác bằng cách lặp lại khóa thường xuyên.

Tự nắm lấy khóa là cần thiết khi bạn đang nói chuyện với Python từ C khi các luồng Python khác đang hoạt động để 'chọn tham gia' vào giao thức này và đảm bảo rằng không có gì không an toàn xảy ra sau lưng bạn.

Các hệ thống khác có di sản đơn luồng sau này phát triển thành hệ thống phân luồng thường có một số cơ chế thuộc loại này. Ví dụ, hạt nhân Linux có "Big Kernel Lock" từ những ngày đầu SMP. Dần dần theo thời gian khi hiệu suất đa luồng trở thành một vấn đề, có xu hướng cố gắng phá vỡ các loại khóa này thành các phần nhỏ hơn hoặc thay thế chúng bằng các thuật toán không khóa và cấu trúc dữ liệu nếu có thể để tối đa hóa thông lượng.


+1 khi đề cập đến thực tế là khóa hạt thô được sử dụng nhiều hơn mọi người nghĩ, đặc biệt là BKL đã bị lãng quên (tôi sử dụng reiserfs- lý do thực sự duy nhất tôi biết về nó).
new123456

3
Linux đã có BKL, kể từ phiên bản 2.6.39, BKL đã bị loại bỏ hoàn toàn.
avi

5
Tất nhiên. Xin phiền bạn đó là ~ 3 năm sau khi tôi trả lời câu hỏi. =)
Edward KMETT

7

Đối với câu hỏi thứ hai của bạn, không phải tất cả các ngôn ngữ kịch bản đều sử dụng điều này, nhưng nó chỉ làm cho chúng kém mạnh mẽ hơn. Ví dụ, các chuỗi trong Ruby có màu xanh lá cây và không phải là bản địa.

Trong Python, các luồng là bản địa và GIL chỉ ngăn chúng chạy trên các lõi khác nhau.

Trong Perl, các chủ đề thậm chí còn tồi tệ hơn. Họ chỉ sao chép toàn bộ trình thông dịch và không thể sử dụng được như trong Python.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.