Khóa phiên dịch toàn cầu (GIL) trong CPython là gì?


244

Khóa phiên dịch toàn cầu là gì và tại sao nó là một vấn đề?

Rất nhiều tiếng ồn đã được tạo ra xung quanh việc loại bỏ GIL khỏi Python và tôi muốn hiểu tại sao điều đó lại quan trọng đến vậy. Bản thân tôi chưa bao giờ viết trình biên dịch hay phiên dịch, vì vậy đừng tiết kiệm chi tiết, có lẽ tôi sẽ cần họ hiểu.


3
Xem David Beazley cho bạn biết mọi thứ bạn từng muốn biết về GIL.
hughdbrown

1
Đây là một bài viết dài nói về GIL và xâu chuỗi trong Python tôi đã viết lại một lúc. Nó đi sâu vào một số lượng chi tiết hợp lý trên đó: jessenoller.com/2009/02/01/ trên
jnoller

Dưới đây là một số mã chứng minh hiệu ứng của GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu

3
Tôi thấy đây là lời giải thích tốt nhất về GIL. Xin vui lòng đọc. dabeaz.com/python/Under HiểuGIL.pdf
suhao399

realpython.com/python-gil Tôi thấy điều này hữu ích
qwr

Câu trả lời:


220

GIL của Python được dự định tuần tự hóa quyền truy cập vào các thông dịch viên từ các luồng khác nhau. Trên các hệ thống đa lõi, điều đó có nghĩa là nhiều luồng không thể sử dụng hiệu quả nhiều lõi. (Nếu GIL không dẫn đến vấn đề này, hầu hết mọi người sẽ không quan tâm đến GIL - nó chỉ được nêu ra như một vấn đề vì sự phổ biến ngày càng tăng của các hệ thống đa lõi.) Nếu bạn muốn hiểu chi tiết về nó, bạn có thể xem video này hoặc xem bộ slide này . Nó có thể là quá nhiều thông tin, nhưng sau đó bạn đã hỏi chi tiết :-)

Lưu ý rằng GIL của Python chỉ thực sự là một vấn đề đối với CPython, việc triển khai tham chiếu. Jython và IronPython không có GIL. Là nhà phát triển Python, bạn thường không bắt gặp GIL trừ khi bạn viết phần mở rộng C. Người viết tiện ích mở rộng C cần phát hành GIL khi tiện ích mở rộng của họ chặn I / O, để các luồng khác trong quy trình Python có cơ hội chạy.


46
Câu trả lời hay - về cơ bản, điều đó có nghĩa là các luồng trong Python chỉ tốt khi chặn I / O; ứng dụng của bạn sẽ không bao giờ vượt quá 1 lõi CPU sử dụng bộ xử lý
Ana Betts

8
"Là nhà phát triển Python, bạn thường không bắt gặp GIL trừ khi bạn viết phần mở rộng C" - Bạn có thể không biết rằng nguyên nhân khiến mã đa luồng của bạn chạy ở tốc độ ốc là do GIL, nhưng bạn ' Chắc chắn sẽ cảm thấy tác dụng của nó. Tôi vẫn ngạc nhiên rằng để tận dụng lợi thế của máy chủ 32 lõi với Python có nghĩa là tôi cần 32 quy trình với tất cả các chi phí liên quan.
Cơ bản

6
@PaulBetts: không đúng Có khả năng là hiệu suất mã quan trọng đã sử dụng phần mở rộng C có thể và không phát hành GIL ví dụ regex, lxml, numpymô-đun. Cython cho phép phát hành GIL theo mã tùy chỉnh, ví dụ:b2a_bin(data)
jfs

5
@Paul Betts: Bạn có thể nhận được trên 1 mã CPU sử dụng bộ xử lý bằng mô-đun đa xử lý . Tạo nhiều quy trình là "trọng lượng nặng hơn" so với việc tạo nhiều luồng, nhưng nếu bạn thực sự cần hoàn thành công việc song song, trong python, thì đó là một tùy chọn.
AJNeufeld

1
@david_adler Vâng, vẫn vậy, và có khả năng vẫn còn như vậy trong một thời gian. Điều đó đã không thực sự ngăn Python thực sự hữu ích cho nhiều khối lượng công việc khác nhau.
Vinay Sajip

59

Giả sử bạn có nhiều luồng không thực sự chạm vào dữ liệu của nhau. Những người nên thực hiện độc lập nhất có thể. Nếu bạn có một "khóa toàn cầu" mà bạn cần có để gọi (nói) một chức năng, điều đó có thể kết thúc như một nút cổ chai. Bạn có thể kết thúc không nhận được nhiều lợi ích từ việc có nhiều chủ đề ở nơi đầu tiên.

Để đưa nó vào một sự tương tự trong thế giới thực: hãy tưởng tượng 100 nhà phát triển làm việc tại một công ty chỉ với một cốc cà phê duy nhất. Hầu hết các nhà phát triển sẽ dành thời gian chờ đợi cà phê thay vì mã hóa.

Không có gì trong số này là dành riêng cho Python - Tôi không biết chi tiết về những gì Python cần GIL cho lần đầu tiên. Tuy nhiên, hy vọng nó đã cho bạn một ý tưởng tốt hơn về khái niệm chung.


Ngoại trừ việc chờ đợi cốc cà phê có vẻ như là một quá trình ràng buộc I / O, vì họ chắc chắn có thể làm những việc khác trong khi chờ cốc. GIL có rất ít ảnh hưởng đến các luồng nặng I / O dành phần lớn thời gian chờ đợi của họ.
Cruncher


36

Trước tiên hãy hiểu những gì con trăn GIL cung cấp:

Bất kỳ hoạt động / hướng dẫn được thực hiện trong trình thông dịch. GIL đảm bảo rằng trình thông dịch được giữ bởi một luồng duy nhất tại một thời điểm cụ thể . Và chương trình python của bạn với nhiều luồng hoạt động trong một trình thông dịch. Tại bất kỳ thời điểm cụ thể nào, trình thông dịch này được giữ bởi một luồng duy nhất. Nó có nghĩa là chỉ có luồng đang giữ trình thông dịch đang chạy bất cứ lúc nào .

Bây giờ tại sao đó là một vấn đề:

Máy của bạn có thể có nhiều lõi / bộ xử lý. Và nhiều lõi cho phép nhiều luồng thực thi đồng thời, nhiều luồng có thể thực thi tại bất kỳ thời điểm cụ thể nào. . Nhưng vì trình thông dịch được giữ bởi một luồng duy nhất, các luồng khác không làm gì cả mặc dù chúng có quyền truy cập vào lõi. Vì vậy, bạn sẽ không nhận được bất kỳ lợi thế nào được cung cấp bởi nhiều lõi bởi vì tại bất kỳ thời điểm nào, chỉ có một lõi duy nhất, đó là lõi đang được sử dụng bởi luồng hiện đang giữ trình thông dịch, đang được sử dụng. Vì vậy, chương trình của bạn sẽ mất nhiều thời gian để thực thi như thể nó là một chương trình luồng đơn.

Tuy nhiên, có khả năng chặn hoặc các hoạt động dài hạn, chẳng hạn như I / O, xử lý hình ảnh và xử lý số NumPy, xảy ra bên ngoài GIL. Lấy từ đây . Vì vậy, đối với các hoạt động như vậy, một hoạt động đa luồng vẫn sẽ nhanh hơn một hoạt động theo luồng đơn lẻ mặc dù có sự hiện diện của GIL. Vì vậy, GIL không phải lúc nào cũng là nút cổ chai.

Chỉnh sửa: GIL là một chi tiết triển khai của CPython. IronPython và Jython không có GIL, vì vậy một chương trình đa luồng thực sự nên có thể có ở họ, nghĩ rằng tôi chưa bao giờ sử dụng PyPy và Jython và không chắc chắn về điều này.


4
Lưu ý : PyPy có GIL . Tham khảo : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . Trong khi Ironpython và Jython không có GIL.
TASdik Rahman

Thật vậy, PyPy có GIL, nhưng IronPython thì không.
Emmanuel

@Emmanuel Đã chỉnh sửa câu trả lời để xóa PyPy và bao gồm IronPython.
Akshar Raaj

17

Python không cho phép đa luồng theo nghĩa thật nhất của từ này. Nó có một gói đa luồng nhưng nếu bạn muốn đa luồng để tăng tốc mã của mình, thì thường không nên sử dụng nó. Python có cấu trúc được gọi là Khóa phiên dịch toàn cầu (GIL).

https://www.youtube.com/watch?v=ph374fJqFPE

GIL đảm bảo rằng chỉ một trong số các 'luồng' của bạn có thể thực thi bất kỳ lúc nào. Một luồng thu được GIL, thực hiện một công việc nhỏ, sau đó chuyển GIL sang luồng tiếp theo. Điều này xảy ra rất nhanh vì vậy đối với mắt người, có vẻ như các luồng của bạn đang thực thi song song, nhưng chúng thực sự chỉ thay phiên nhau sử dụng cùng lõi CPU. Tất cả thông qua GIL này thêm chi phí để thực hiện. Điều này có nghĩa là nếu bạn muốn làm cho mã của mình chạy nhanh hơn thì sử dụng gói luồng thường không phải là một ý tưởng hay.

Có nhiều lý do để sử dụng gói luồng của Python. Nếu bạn muốn chạy một số thứ cùng một lúc và hiệu quả không phải là vấn đề đáng lo ngại, thì nó hoàn toàn tốt và tiện lợi. Hoặc nếu bạn đang chạy mã cần chờ một cái gì đó (như một số IO) thì nó có thể có nhiều ý nghĩa. Nhưng thư viện luồng sẽ không cho phép bạn sử dụng thêm lõi CPU.

Đa luồng có thể được gia công cho hệ điều hành (bằng cách xử lý đa luồng), một số ứng dụng bên ngoài gọi mã Python của bạn (ví dụ: Spark hoặc Hadoop) hoặc một số mã mà mã Python của bạn gọi (ví dụ: bạn có thể có Python của mình mã gọi một hàm C thực hiện các công cụ đa luồng đắt tiền).


15

Bất cứ khi nào hai luồng có quyền truy cập vào cùng một biến, bạn có một vấn đề. Ví dụ, trong C ++, cách để tránh sự cố là xác định một số khóa mutex để ngăn hai luồng, giả sử, nhập setter của một đối tượng cùng một lúc.

Đa luồng có thể có trong python, nhưng hai luồng không thể được thực thi cùng một lúc tại độ mịn chi tiết hơn một lệnh python. Chuỗi chạy đang nhận được một khóa toàn cầu được gọi là GIL.

Điều này có nghĩa là nếu bạn bắt đầu viết một số mã đa luồng để tận dụng bộ xử lý đa lõi của mình, hiệu suất của bạn sẽ không được cải thiện. Cách giải quyết thông thường bao gồm đi đa hướng.

Lưu ý rằng có thể giải phóng GIL nếu bạn đang ở trong một phương thức mà bạn đã viết bằng C.

Việc sử dụng GIL không phải là vốn có của Python mà là một số trình thông dịch của nó, bao gồm cả CPython phổ biến nhất. (#edited, xem bình luận)

Vấn đề GIL vẫn còn hiệu lực trong Python 3000.


Stackless vẫn có GIL. Stackless không cải thiện luồng (như trong mô-đun) - nó cung cấp một phương pháp lập trình khác (coroutines) nhằm cố gắng giải quyết vấn đề, nhưng yêu cầu các chức năng không chặn.
jnoller

GIL mới trong 3.2 thì sao?
new123456

Chỉ cần thêm rằng bạn không gặp vấn đề / cần mutexes / semaphores nếu chỉ có một luồng sẽ cập nhật bộ nhớ. @ new123456 nó làm giảm sự tranh chấp và lên lịch các luồng tốt hơn mà không ảnh hưởng đến hiệu suất đơn luồng (bản thân nó rất ấn tượng) nhưng nó vẫn là một khóa toàn cầu.
Cơ bản

14

Tài liệu Python 3.7

Tôi cũng muốn làm nổi bật trích dẫn sau đây từ tài liệu Pythonthreading :

Chi tiết triển khai CPython: Trong CPython, do Khóa phiên dịch toàn cầu, chỉ một luồng có thể thực thi mã Python cùng một lúc (ngay cả khi các thư viện hướng hiệu suất nhất định có thể khắc phục giới hạn này). Nếu bạn muốn ứng dụng của mình sử dụng tốt hơn các tài nguyên tính toán của các máy đa lõi, bạn nên sử dụng multiprocessinghoặc concurrent.futures.ProcessPoolExecutor. Tuy nhiên, luồng vẫn là một mô hình thích hợp nếu bạn muốn chạy đồng thời nhiều tác vụ ràng buộc I / O.

Liên kết này với mục Thuật ngữglobal interpreter lock giải thích rằng GIL ngụ ý rằng tính song song của luồng trong Python là không phù hợp với các tác vụ bị ràng buộc của CPU :

Cơ chế được trình thông dịch CPython sử dụng để đảm bảo rằng chỉ có một luồng thực thi mã byte Python tại một thời điểm. Điều này đơn giản hóa việc triển khai CPython bằng cách tạo mô hình đối tượng (bao gồm các loại tích hợp quan trọng như dict) hoàn toàn an toàn trước truy cập đồng thời. Việc khóa toàn bộ trình thông dịch giúp trình thông dịch trở nên đa luồng dễ dàng hơn, với chi phí của phần lớn tính song song được cung cấp bởi các máy đa bộ xử lý.

Tuy nhiên, một số mô-đun mở rộng, theo tiêu chuẩn hoặc bên thứ ba, được thiết kế để giải phóng GIL khi thực hiện các tác vụ chuyên sâu tính toán như nén hoặc băm. Ngoài ra, GIL luôn được phát hành khi thực hiện I / O.

Những nỗ lực trong quá khứ để tạo ra một trình thông dịch, luồng dữ liệu miễn phí (một khóa khóa dữ liệu được chia sẻ ở mức độ chi tiết tốt hơn nhiều) đã không thành công vì hiệu năng bị ảnh hưởng trong trường hợp bộ xử lý đơn thông thường. Người ta tin rằng việc khắc phục vấn đề hiệu suất này sẽ khiến việc thực hiện phức tạp hơn nhiều và do đó tốn kém hơn để duy trì.

Trích dẫn này cũng ngụ ý rằng các lệnh và do đó việc gán biến cũng là luồng an toàn như một chi tiết triển khai CPython:

Tiếp theo, các tài liệu cho multiprocessinggói giải thích cách nó vượt qua GIL bằng cách sinh ra quá trình trong khi phơi bày một giao diện tương tự như threading:

đa xử lý là gói hỗ trợ các quá trình sinh sản bằng cách sử dụng API tương tự như mô-đun luồng. Gói đa xử lý cung cấp cả đồng thời cục bộ và từ xa, bước hiệu quả bên cạnh Khóa phiên dịch toàn cầu bằng cách sử dụng các quy trình con thay vì các luồng. Do đó, mô-đun đa xử lý cho phép lập trình viên tận dụng triệt để nhiều bộ xử lý trên một máy nhất định. Nó chạy trên cả Unix và Windows.

Và các tài liệu đểconcurrent.futures.ProcessPoolExecutor giải thích rằng nó sử dụng multiprocessingnhư một phụ trợ:

Lớp ProcessPoolExecutor là một lớp con Executor sử dụng một nhóm các quy trình để thực hiện các cuộc gọi không đồng bộ. ProcessPoolExecutor sử dụng mô-đun đa xử lý, cho phép nó bước bên cạnh Khóa phiên dịch toàn cầu nhưng cũng có nghĩa là chỉ các đối tượng có thể chọn được mới có thể được thực thi và trả về.

cần được trái ngược với phân lớp cơ sở khác ThreadPoolExecutorsử dụng đề thay vì quy trình

ThreadPoolExecutor là một lớp con Executor sử dụng một nhóm các luồng để thực hiện các cuộc gọi không đồng bộ.

từ đó chúng tôi kết luận rằng ThreadPoolExecutorchỉ phù hợp với các tác vụ bị ràng buộc I / O, trong khi ProcessPoolExecutorcũng có thể xử lý các tác vụ bị ràng buộc của CPU.

Câu hỏi sau đây hỏi tại sao GIL tồn tại ở vị trí đầu tiên: Tại sao Khóa phiên dịch toàn cầu?

Quá trình so với thí nghiệm luồng

Tại Multiprocessing vs Threading Python Tôi đã thực hiện một phân tích thử nghiệm về quy trình so với các luồng trong Python.

Xem trước nhanh kết quả:

nhập mô tả hình ảnh ở đây


0

Tại sao Python (CPython và những người khác) sử dụng GIL

Từ http://wiki.python.org/moin/GlobalInterpreterLock

Trong CPython, khóa trình thông dịch toàn cầu, hoặc GIL, là một mutex ngăn chặn nhiều luồng gốc thực thi mã byte Python cùng một lúc. Khóa này là cần thiết chủ yếu vì quản lý bộ nhớ của CPython không an toàn cho chuỗi.

Làm thế nào để loại bỏ nó khỏi Python?

Giống như Lua, có lẽ Python có thể khởi động nhiều VM, nhưng python không làm điều đó, tôi đoán nên có một số lý do khác.

Trong Numpy hoặc một số thư viện mở rộng python khác, đôi khi, việc phát hành GIL cho các luồng khác có thể tăng hiệu quả của toàn bộ chương trình.


0

Tôi muốn chia sẻ một ví dụ từ cuốn sách đa luồng cho Hiệu ứng hình ảnh. Vì vậy, đây là một tình huống khóa chết cổ điển

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

Bây giờ hãy xem xét các sự kiện trong chuỗi dẫn đến khóa chết.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    Main Thread                             Other Thread                         
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
 1  Python Command acquires GIL             Work started                         
 2  Computation requested                   MyCallback runs and acquires MyMutex 
 3                                          MyCallback now waits for GIL         
 4  MyCallback runs and waits for MyMutex   waiting for GIL                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
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.