Các luồng hoạt động trong Python như thế nào và các cạm bẫy cụ thể của luồng Python phổ biến là gì?


85

Tôi đã cố gắng tìm hiểu về cách các luồng hoạt động trong Python và thật khó để tìm thông tin tốt về cách chúng hoạt động. Tôi có thể chỉ thiếu một liên kết hoặc một cái gì đó, nhưng có vẻ như tài liệu chính thức không phải là rất kỹ lưỡng về chủ đề này, và tôi không thể tìm thấy một bài viết tốt.

Từ những gì tôi có thể biết, chỉ có thể chạy một luồng cùng một lúc và luồng hoạt động sẽ chuyển đổi sau mỗi 10 hướng dẫn hoặc lâu hơn?

Nơi nào có lời giải thích tốt, hoặc bạn có thể cung cấp một lời giải thích? Cũng sẽ rất tuyệt nếu bạn nhận thức được các vấn đề phổ biến mà bạn gặp phải khi sử dụng các chuỗi với Python.

Câu trả lời:


50

Có, vì Khóa thông dịch viên toàn cầu (GIL) nên chỉ có thể chạy một luồng tại một thời điểm. Dưới đây là một số liên kết với một số thông tin chi tiết về điều này:

Từ liên kết cuối cùng, một trích dẫn thú vị:

Hãy để tôi giải thích tất cả những điều đó có nghĩa là gì. Các luồng chạy bên trong cùng một máy ảo và do đó chạy trên cùng một máy vật lý. Các quy trình có thể chạy trên cùng một máy vật lý hoặc trong một máy vật lý khác. Nếu bạn cấu trúc ứng dụng của mình xung quanh các luồng, bạn không phải làm gì để truy cập nhiều máy. Vì vậy, bạn có thể mở rộng quy mô đến bao nhiêu lõi trên một máy (sẽ khá ít theo thời gian), nhưng để thực sự đạt quy mô web, bạn sẽ cần phải giải quyết vấn đề nhiều máy.

Nếu bạn muốn sử dụng đa lõi, pyprocessing xác định một API dựa trên quy trình để thực hiện song song thực sự. Các PEP cũng bao gồm một số tiêu chuẩn thú vị.


1
Thực sự là một nhận xét về câu trích dẫn mượt mà: chắc chắn phân luồng Python giới hạn hiệu quả bạn ở một lõi, ngay cả khi máy có nhiều lõi? Có thể có những lợi ích từ đa lõi vì luồng tiếp theo có thể sẵn sàng hoạt động mà không cần chuyển đổi ngữ cảnh, nhưng các luồng Python của bạn không bao giờ có thể sử dụng> 1 lõi tại một thời điểm.
James Brady

2
Đúng, các luồng python thực tế bị giới hạn trong một lõi, UNLESS một mô-đun C tương tác độc đáo với GIL và chạy chuỗi gốc của chính nó.
Arafangion

Trên thực tế, nhiều lõi làm cho các luồng kém hiệu quả hơn vì có rất nhiều xáo trộn khi kiểm tra xem mỗi luồng có thể truy cập GIL hay không. Ngay cả với GIL mới, hiệu suất vẫn kém hơn ... dabeaz.com/python/NewGIL.pdf
Cơ bản

2
Xin lưu ý rằng GIL cân nhắc không áp dụng cho tất cả các thông dịch viên. Theo như tôi được biết, cả chức năng IronPython và Jython đều không có GIL, cho phép mã của chúng sử dụng hiệu quả hơn phần cứng đa xử lý. Như Arafangion đã đề cập, trình thông dịch CPython cũng có thể chạy đa luồng đúng cách nếu mã không cần quyền truy cập vào các mục dữ liệu Python giải phóng khóa, sau đó mua lại nó trước khi quay lại.
holdenweb

Điều gì gây ra chuyển đổi ngữ cảnh giữa các luồng trong Python? Nó có dựa trên ngắt bộ hẹn giờ không? Chặn hoặc một cuộc gọi lợi nhuận cụ thể?
CMCDragonkai

36

Python là một ngôn ngữ khá dễ sử dụng, nhưng có những lưu ý. Điều lớn nhất bạn cần biết là Khóa thông dịch viên toàn cầu. Điều này chỉ cho phép một luồng truy cập trình thông dịch. Điều này có nghĩa là hai điều: 1) bạn hiếm khi thấy mình sử dụng câu lệnh khóa trong python và 2) nếu bạn muốn tận dụng các hệ thống đa xử lý, bạn phải sử dụng các quy trình riêng biệt. CHỈNH SỬA: Tôi cũng nên chỉ ra rằng bạn có thể đặt một số mã trong C / C ++ nếu bạn muốn sử dụng GIL.

Vì vậy, bạn cần phải xem xét lại lý do tại sao bạn muốn sử dụng luồng. Nếu bạn muốn song song hóa ứng dụng của mình để tận dụng lợi thế của kiến ​​trúc lõi kép, bạn cần xem xét chia ứng dụng của mình thành nhiều quy trình.

Nếu bạn muốn cải thiện khả năng phản hồi, bạn nên XEM XÉT bằng cách sử dụng các chủ đề. Tuy nhiên, có những lựa chọn thay thế khác, đó là vi phân luồng . Ngoài ra còn có một số khuôn khổ mà bạn nên xem xét:


@JS - Đã sửa. Danh sách đó dù sao cũng đã lỗi thời.
Jason Baker

Tôi chỉ cảm thấy sai lầm khi bạn cần nhiều quy trình - với tất cả chi phí đòi hỏi - để tận dụng lợi thế của một hệ thống đa lõi. Chúng tôi có một số máy chủ với 32 lõi logic - vì vậy tôi cần 32 quy trình để sử dụng chúng một cách hiệu quả? Madness
Cơ bản

@Basic - Chi phí bắt đầu một quy trình so với bắt đầu một chủ đề những ngày này là tối thiểu. Tôi cho rằng bạn có thể bắt đầu gặp vấn đề nếu chúng ta đang nói về hàng nghìn truy vấn mỗi giây, nhưng sau đó tôi sẽ đặt câu hỏi về việc lựa chọn Python cho một dịch vụ bận rộn như vậy ngay từ đầu.
Jason Baker

20

Dưới đây là một mẫu ren cơ bản. Nó sẽ sinh ra 20 chủ đề; mỗi luồng sẽ xuất ra số luồng của nó. Chạy nó và quan sát thứ tự mà chúng in.

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

Như bạn đã gợi ý về các luồng Python được thực hiện thông qua cắt thời gian. Đây là cách họ nhận được hiệu ứng "song song".

Trong ví dụ của tôi, lớp Foo của tôi mở rộng luồng, sau đó tôi triển khai runphương thức, đó là nơi mã mà bạn muốn chạy trong một luồng đi đến. Để bắt đầu luồng, bạn gọi start()đối tượng luồng, đối tượng này sẽ tự động gọi runphương thức ...

Tất nhiên, đây chỉ là những điều rất cơ bản. Cuối cùng bạn sẽ muốn tìm hiểu về semaphores, mutexes và khóa để đồng bộ hóa luồng và truyền thông điệp.


10

Sử dụng các luồng trong python nếu từng nhân viên đang thực hiện các hoạt động liên kết I / O. Nếu bạn đang cố gắng mở rộng quy mô trên nhiều lõi trên một máy, hãy tìm một khung IPC tốt cho python hoặc chọn một ngôn ngữ khác.


4

Lưu ý: bất cứ nơi nào tôi đề cập, threadtôi có nghĩa là các chủ đề cụ thể trong python cho đến khi được nêu rõ ràng.

Các luồng hoạt động hơi khác trong python nếu bạn đến từ C/C++nền. Trong python, Chỉ một luồng có thể ở trạng thái chạy tại một thời điểm nhất định, điều này có nghĩa là Các luồng trong python không thể thực sự tận dụng sức mạnh của nhiều lõi xử lý vì theo thiết kế, các luồng không thể chạy song song trên nhiều lõi.

Vì việc quản lý bộ nhớ trong python không an toàn cho luồng nên mỗi luồng yêu cầu một quyền truy cập độc quyền vào cấu trúc dữ liệu trong trình thông dịch python. Quyền truy cập độc quyền này được thực hiện bởi một cơ chế được gọi là (khóa thông dịch toàn cục) .GIL

Why does python use GIL?

Để ngăn nhiều luồng truy cập trạng thái thông dịch đồng thời và làm hỏng trạng thái thông dịch.

Ý tưởng là bất cứ khi nào một luồng đang được thực thi (ngay cả khi đó là luồng chính) , một GIL sẽ được thu thập và sau một khoảng thời gian xác định trước, GIL được giải phóng bởi luồng hiện tại và được một số luồng khác yêu cầu lại (nếu có).

Why not simply remove GIL?

Không phải là không thể loại bỏ GIL, chỉ là nếu làm như vậy một cách thận trọng, chúng ta sẽ đặt nhiều ổ khóa bên trong trình thông dịch để tuần tự hóa quyền truy cập, điều này làm cho ngay cả một ứng dụng luồng đơn lẻ cũng hoạt động kém hơn.

vì vậy chi phí loại bỏ GIL được trả bằng việc giảm hiệu suất của một ứng dụng đơn luồng, điều này không bao giờ mong muốn.

So when does thread switching occurs in python?

Chuyển đổi luồng xảy ra khi GIL được phát hành. Vậy GIL được phát hành khi nào? Có hai kịch bản cần xem xét.

Nếu một Luồng đang thực hiện các hoạt động Bound CPU (Xử lý ảnh Ex).

Trong các phiên bản cũ hơn của python, chuyển đổi Luồng được sử dụng để xảy ra sau khi không có lệnh python cố định. Theo mặc định, nó được đặt thành 100. Hóa ra nó không phải là một chính sách rất tốt để quyết định khi nào chuyển đổi xảy ra vì thời gian thực hiện một lệnh duy nhất có thể rất nhanh từ mili giây đến thậm chí là một giây. Do đó, việc giải phóng GIL sau mỗi 100lệnh bất kể thời gian thực thi chúng là một chính sách kém.

Trong các phiên bản mới thay vì sử dụng số lượng lệnh làm số liệu để chuyển luồng, một khoảng thời gian có thể định cấu hình được sử dụng. Khoảng thời gian chuyển đổi mặc định là 5 mili giây. Bạn có thể lấy khoảng thời gian chuyển đổi hiện tại bằng cách sử dụng sys.getswitchinterval(). Điều này có thể được thay đổi bằng cách sử dụngsys.setswitchinterval()

Nếu một Luồng đang thực hiện một số Hoạt động liên kết IO (Truy cập hệ thống tệp Ex hoặc
IO mạng)

GIL được phát hành bất cứ khi nào luồng đang đợi một số hoạt động IO hoàn thành.

Which thread to switch to next?

Trình thông dịch không có bộ lập lịch của riêng nó. Luồng nào được lập lịch vào cuối khoảng thời gian là quyết định của hệ điều hành. .


3

Một giải pháp dễ dàng cho GIL là mô-đun đa xử lý . Nó có thể được sử dụng như một phần thay thế cho mô-đun phân luồng nhưng sử dụng nhiều quy trình Phiên dịch thay vì luồng. Bởi vì điều này có nhiều chi phí hơn một chút so với việc tạo luồng đơn giản cho những thứ đơn giản nhưng nó mang lại cho bạn lợi thế về khả năng song song thực sự nếu bạn cần. Nó cũng dễ dàng mở rộng quy mô cho nhiều máy vật lý.

Nếu bạn cần thực sự song song quy mô lớn thì tôi sẽ xem xét xa hơn nhưng nếu bạn chỉ muốn mở rộng quy mô đến tất cả các lõi của một máy tính hoặc một vài máy tính khác mà không cần tất cả công việc triển khai một khuôn khổ toàn diện hơn, thì điều này dành cho bạn .


2

Hãy nhớ rằng GIL được thiết lập để thăm dò thường xuyên để hiển thị sự xuất hiện của nhiều nhiệm vụ. Cài đặt này có thể được tinh chỉnh, nhưng tôi đưa ra gợi ý rằng phải có công việc mà các luồng đang thực hiện hoặc nhiều công tắc ngữ cảnh sẽ gây ra sự cố.

Tôi sẽ đi xa hơn khi đề xuất nhiều bậc cha mẹ trên bộ xử lý và cố gắng duy trì các công việc tương tự trên (các) lõi giống nhau.

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.