Xem bây giờ hướng dẫn này có một khóa học video liên quan được tạo bởi nhóm Python thực sự. Xem nó cùng với hướng dẫn bằng văn bản để làm sâu sắc thêm sự hiểu biết của bạn: Xây dựng bằng Python This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Threading in Python
Python Threading cho phép bạn có các phần khác nhau trong chương trình chạy đồng thời và có thể đơn giản hóa thiết kế của bạn. Nếu bạn đã có một số kinh nghiệm về Python và muốn tăng tốc chương trình của mình bằng cách sử dụng các chủ đề, thì hướng dẫn này là dành cho bạn!
Bài viết này giả định rằng bạn đã có những điều cơ bản của Python và bạn sử dụng ít nhất phiên bản 3.6 để chạy các ví dụ. Nếu bạn cần bồi dưỡng, bạn có thể bắt đầu với các đường dẫn học tập Python và tăng tốc.
Nếu bạn không chắc chắn nếu bạn muốn sử dụng Python
8, thì bạn có thể kiểm tra tăng tốc chương trình Python của mình với sự đồng thời.
Tất cả các nguồn được sử dụng trong hướng dẫn này đều có sẵn cho bạn trong repo Python GitHub thực sự.
Một chủ đề là gì?
Một luồng là một luồng thực thi riêng biệt. Điều này có nghĩa là chương trình của bạn sẽ có hai điều xảy ra cùng một lúc. Nhưng đối với hầu hết các triển khai Python 3, các chủ đề khác nhau không thực sự thực hiện cùng một lúc: chúng chỉ xuất hiện.
Nó rất hấp dẫn khi nghĩ đến việc xâu chuỗi là có hai (hoặc nhiều) bộ xử lý khác nhau chạy trong chương trình của bạn, mỗi bộ thực hiện một nhiệm vụ độc lập cùng một lúc. Điều đó gần như đúng. Các luồng có thể đang chạy trên các bộ xử lý khác nhau, nhưng chúng sẽ chỉ chạy một lần.
Nhận được nhiều nhiệm vụ chạy đồng thời yêu cầu triển khai Python không chuẩn, viết một số mã của bạn bằng một ngôn ngữ khác hoặc sử dụng
Do cách thực hiện CPython của Python hoạt động, việc xâu chuỗi có thể không tăng tốc tất cả các nhiệm vụ. Điều này là do các tương tác với GIL về cơ bản giới hạn một luồng python để chạy tại một thời điểm.
Các nhiệm vụ dành phần lớn thời gian của họ để chờ đợi các sự kiện bên ngoài nói chung là những ứng cử viên tốt để tham gia. Các vấn đề yêu cầu tính toán CPU nặng và dành ít thời gian chờ đợi các sự kiện bên ngoài có thể không chạy nhanh hơn chút nào.
Điều này đúng với mã được viết bằng Python và chạy trên triển khai CPython tiêu chuẩn. Nếu chủ đề của bạn được viết bằng C, chúng có khả năng giải phóng Gil và chạy đồng thời. Nếu bạn đang chạy trên một triển khai Python khác, hãy kiểm tra với tài liệu cũng xem cách nó xử lý các chủ đề.
Nếu bạn đang chạy một triển khai Python tiêu chuẩn, chỉ viết bằng Python và có vấn đề ràng buộc CPU, bạn nên kiểm tra mô-đun
Kiến trúc chương trình của bạn để sử dụng luồng cũng có thể cung cấp lợi ích trong thiết kế rõ ràng. Hầu hết các ví dụ mà bạn sẽ tìm hiểu trong hướng dẫn này không nhất thiết sẽ chạy nhanh hơn vì chúng sử dụng các chủ đề. Sử dụng luồng trong chúng giúp làm cho thiết kế sạch hơn và dễ lý luận hơn.
Vì vậy, hãy để Lôi ngừng nói về việc xâu chuỗi và bắt đầu sử dụng nó!
Bắt đầu một chủ đề
Bây giờ, bạn đã có một ý tưởng về một chủ đề là gì, hãy để học cách tạo ra một chủ đề. Thư viện tiêu chuẩn Python cung cấp
Khi bạn chạy chương trình này như hiện tại (với dòng hai mươi nhận xét), đầu ra sẽ trông như thế này:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
Bạn sẽ nhận thấy rằng
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
5 Một luồng chạy trong nền mà không phải lo lắng về việc tắt nó xuống.
Nếu một chương trình đang chạy
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
0 không phải là
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
1, thì chương trình sẽ đợi các luồng đó hoàn thành trước khi kết thúc.
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
0 là daemon, tuy nhiên, chỉ bị giết bất cứ nơi nào khi họ đang xuất hiện.
Hãy cùng xem xét kỹ hơn một chút về đầu ra của chương trình của bạn ở trên. Hai dòng cuối cùng là bit thú vị. Khi bạn chạy chương trình, bạn sẽ nhận thấy rằng có một khoảng dừng (khoảng 2 giây) sau khi
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
3 đã in tin nhắn
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
4 của nó và trước khi chủ đề kết thúc.
Tạm dừng này là Python chờ đợi chủ đề không hoàn thành. Khi chương trình Python của bạn kết thúc, một phần của quá trình tắt máy là làm sạch thói quen luồng.
5. Bạn làm điều đó bằng cách thay đổi cách bạn xây dựng
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
2, thêm cờ
importconcurrent.futures# [rest of code]if__name__=="__main__":format="%(asctime)s: %(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")withconcurrent.futures.ThreadPoolExecutor(max_workers=3)asexecutor:executor.map(thread_function,range(3))
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
3 đi đến cuối mã của nó và chương trình muốn hoàn thành, daemon đã bị giết.
import concurrent.futures
# [rest of code]
if __name__ == "__main__":
format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO,
datefmt="%H:%M:%S")
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
executor.map(thread_function, range(3))
6 Một chủ đề
Chủ đề daemon rất tiện dụng, nhưng khi nào bạn muốn đợi một chủ đề dừng lại? Thế còn khi bạn muốn làm điều đó và không thoát khỏi chương trình của bạn? Bây giờ, hãy để Lừa quay trở lại chương trình ban đầu của bạn và xem xét điều đó đã nhận xét về dòng hai mươi:
Để nói với một chủ đề để đợi một chủ đề khác kết thúc, bạn gọi
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
7. Nếu bạn giải quyết dòng đó, luồng chính sẽ tạm dừng và chờ luồng
importconcurrent.futures# [rest of code]if__name__=="__main__":format="%(asctime)s: %(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")withconcurrent.futures.ThreadPoolExecutor(max_workers=3)asexecutor:executor.map(thread_function,range(3))
8 hoàn thành chạy.
Bạn đã kiểm tra điều này trên mã với luồng daemon hoặc luồng thông thường? Hóa ra nó không quan trọng. Nếu bạn
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
7 một chủ đề, câu lệnh đó sẽ đợi cho đến khi một loại luồng được hoàn thành.
Làm việc với nhiều chủ đề
Mã ví dụ cho đến nay chỉ hoạt động với hai luồng: luồng chính và một luồng bạn bắt đầu với đối tượng
Thông thường, bạn sẽ muốn bắt đầu một số chủ đề và yêu cầu chúng làm công việc thú vị. Hãy bắt đầu bằng cách nhìn vào cách làm điều đó khó hơn, và sau đó bạn sẽ chuyển sang một phương pháp dễ dàng hơn.
Cách khó hơn để bắt đầu nhiều luồng là cách mà bạn đã biết:
Mã này sử dụng cùng một cơ chế mà bạn đã thấy ở trên để bắt đầu một luồng, tạo đối tượng
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
2 và sau đó gọi
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
4. Chương trình giữ một danh sách các đối tượng
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
2 để sau đó có thể đợi chúng sau đó bằng cách sử dụng
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
7.
Chạy mã này nhiều lần có thể sẽ tạo ra một số kết quả thú vị. Ở đây, một ví dụ đầu ra từ máy của tôi:
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
Nếu bạn đi qua đầu ra một cách cẩn thận, bạn sẽ thấy cả ba chủ đề bắt đầu theo thứ tự bạn có thể mong đợi, nhưng trong trường hợp này, chúng kết thúc theo thứ tự ngược lại! Nhiều lần chạy sẽ tạo ra các thứ tự khác nhau. Tìm tin nhắn
Thứ tự mà các luồng được chạy được xác định bởi hệ điều hành và có thể khá khó dự đoán. Nó có thể (và có thể sẽ) thay đổi từ chạy đến chạy, vì vậy bạn cần nhận thức được điều đó khi bạn thiết kế các thuật toán sử dụng luồng.
May mắn thay, Python cung cấp cho bạn một số nguyên thủy mà bạn sẽ xem xét sau này để giúp phối hợp các luồng và khiến chúng chạy cùng nhau. Trước đó, hãy để Lôi nhìn vào cách làm cho việc quản lý một nhóm các chủ đề dễ dàng hơn một chút.
Sử dụng x = threading.Thread(target=thread_function, args=(1,), daemon=True)
4
Có một cách dễ dàng hơn để bắt đầu một nhóm các chủ đề so với cái bạn thấy ở trên. Nó được gọi là
importconcurrent.futures# [rest of code]if__name__=="__main__":format="%(asctime)s: %(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")withconcurrent.futures.ThreadPoolExecutor(max_workers=3)asexecutor:executor.map(thread_function,range(3))
5, hãy để Lừa nói một chút về một trong những vấn đề khó khăn hơn mà bạn sẽ gặp phải khi viết các chương trình được thực hiện: Điều kiện cuộc đua.
Khi bạn đã thấy một điều kiện cuộc đua là gì và nhìn vào một người xảy ra, bạn sẽ chuyển sang một số nguyên thủy được cung cấp bởi thư viện tiêu chuẩn để ngăn chặn các điều kiện chủng tộc xảy ra.
Điều kiện cuộc đua có thể xảy ra khi hai hoặc nhiều luồng truy cập một phần dữ liệu hoặc tài nguyên được chia sẻ. Trong ví dụ này, bạn sẽ tạo ra một điều kiện cuộc đua lớn xảy ra mọi lúc, nhưng lưu ý rằng hầu hết các điều kiện chủng tộc không rõ ràng như vậy. Thông thường, chúng chỉ xảy ra hiếm khi và chúng có thể tạo ra kết quả khó hiểu. Như bạn có thể tưởng tượng, điều này làm cho chúng khá khó để gỡ lỗi.
May mắn thay, điều kiện cuộc đua này sẽ xảy ra mỗi lần và bạn sẽ đi qua nó một cách chi tiết để giải thích những gì đang xảy ra.
Trong ví dụ này, bạn sẽ viết một lớp cập nhật cơ sở dữ liệu. Được rồi, bạn không thực sự sẽ có một cơ sở dữ liệu: bạn sẽ chỉ giả mạo nó, bởi vì đó không phải là điểm của bài viết này.
05 trông hơi lạ. Nó mô phỏng việc đọc một giá trị từ cơ sở dữ liệu, thực hiện một số tính toán trên nó và sau đó viết một giá trị mới trở lại cơ sở dữ liệu.
Trong trường hợp này, đọc từ cơ sở dữ liệu chỉ có nghĩa là sao chép
Bạn có thể đã mong đợi điều đó sẽ xảy ra, nhưng hãy để Lôi nhìn vào các chi tiết về những gì mà thực sự đang diễn ra ở đây, vì điều đó sẽ làm cho giải pháp cho vấn đề này dễ hiểu hơn.
Một chủ đề
Trước khi bạn đi sâu vào vấn đề này với hai chủ đề, hãy để Lùi lại và nói một chút về một số chi tiết về cách các chủ đề hoạt động.
Bạn đã giành chiến thắng khi đi sâu vào tất cả các chi tiết ở đây, vì điều đó không quan trọng ở cấp độ này. Chúng tôi cũng sẽ đơn giản hóa một vài điều theo cách mà giành được chính xác về mặt kỹ thuật nhưng sẽ cho bạn ý tưởng đúng về những gì đang xảy ra.
38. Đây chắc chắn là một điều tốt. Nếu không, hai luồng chạy cùng một chức năng sẽ luôn gây nhầm lẫn cho nhau. Nó có nghĩa là tất cả các biến được phạm vi (hoặc cục bộ) đến một hàm đều an toàn.local to the function. In the case of
38. This is definitely a good thing. Otherwise, two threads running the same function would always confuse each other. It means that all
variables that are scoped (or local) to a function are thread-safe.
Bây giờ bạn có thể bắt đầu đi qua những gì xảy ra nếu bạn chạy chương trình ở trên với một luồng duy nhất và một cuộc gọi đến
Hai luồng có quyền truy cập xen kẽ vào một đối tượng được chia sẻ duy nhất, ghi đè lên kết quả của nhau. Các điều kiện cuộc đua tương tự có thể phát sinh khi một luồng giải phóng bộ nhớ hoặc đóng tay cầm tệp trước khi luồng khác được truy cập xong.
Tại sao đây là một ví dụ ngớ ngẩn
Ví dụ trên được tạo ra để đảm bảo rằng điều kiện cuộc đua xảy ra mỗi khi bạn chạy chương trình của mình. Bởi vì hệ điều hành có thể trao đổi một luồng bất cứ lúc nào, nên có thể làm gián đoạn một câu lệnh như
importconcurrent.futures# [rest of code]if__name__=="__main__":format="%(asctime)s: %(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")withconcurrent.futures.ThreadPoolExecutor(max_workers=3)asexecutor:executor.map(thread_function,range(3))
8 nhưng trước khi nó viết lại giá trị tăng.
Các chi tiết về cách điều này xảy ra khá thú vị, nhưng không cần thiết cho phần còn lại của bài viết này, vì vậy hãy thoải mái bỏ qua phần ẩn này.
Mã trên không phải là ngoài đó như bạn có thể nghĩ ban đầu. Nó được thiết kế để buộc một điều kiện chủng tộc mỗi khi bạn chạy nó, nhưng điều đó giúp giải quyết dễ dàng hơn nhiều so với hầu hết các điều kiện chủng tộc.
Có hai điều cần ghi nhớ khi nghĩ về điều kiện chủng tộc:
82 cũng có bộ xử lý nhiều bước. Mỗi bước này là một hướng dẫn riêng cho bộ xử lý.
Hệ điều hành có thể hoán đổi chủ đề nào đang chạy bất cứ lúc nào. Một chủ đề có thể được hoán đổi sau bất kỳ hướng dẫn nhỏ nào. Điều này có nghĩa là một chủ đề có thể được đưa vào giấc ngủ để cho một chủ đề khác chạy ở giữa một câu lệnh Python.
Hãy cùng nhìn vào điều này một cách chi tiết. REP REP bên dưới hiển thị một hàm có tham số và gia tăng nó:
importconcurrent.futures# [rest of code]if__name__=="__main__":format="%(asctime)s: %(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")withconcurrent.futures.ThreadPoolExecutor(max_workers=3)asexecutor:executor.map(thread_function,range(3))
92. Nếu hệ điều hành trao đổi luồng này và chạy một luồng khác cũng sửa đổi
importconcurrent.futures# [rest of code]if__name__=="__main__":format="%(asctime)s: %(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")withconcurrent.futures.ThreadPoolExecutor(max_workers=3)asexecutor:executor.map(thread_function,range(3))
8, thì khi luồng này tiếp tục, nó sẽ ghi đè lên
importconcurrent.futures# [rest of code]if__name__=="__main__":format="%(asctime)s: %(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")withconcurrent.futures.ThreadPoolExecutor(max_workers=3)asexecutor:executor.map(thread_function,range(3))
8 với giá trị không chính xác.
Về mặt kỹ thuật, ví dụ này đã giành được một điều kiện cuộc đua vì
importconcurrent.futures# [rest of code]if__name__=="__main__":format="%(asctime)s: %(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")withconcurrent.futures.ThreadPoolExecutor(max_workers=3)asexecutor:executor.map(thread_function,range(3))
96. Tuy nhiên, nó minh họa làm thế nào một luồng có thể bị gián đoạn trong một hoạt động Python duy nhất. Cùng một tải, sửa đổi, tập hợp các hoạt động cũng xảy ra trên các giá trị toàn cầu và được chia sẻ. Bạn có thể khám phá với mô -đun
Nó rất hiếm khi có được một điều kiện cuộc đua như thế này xảy ra, nhưng hãy nhớ rằng một sự kiện không thường xuyên đã chiếm được hàng triệu lần lặp lại có thể xảy ra. Sự hiếm hoi của các điều kiện chủng tộc này làm cho chúng, khó gỡ lỗi hơn nhiều so với các lỗi thông thường.
Bây giờ trở lại hướng dẫn theo lịch trình thường xuyên của bạn!
Bây giờ bạn đã thấy một điều kiện cuộc đua đang hoạt động, hãy để tìm ra cách giải quyết chúng!
Đồng bộ hóa cơ bản bằng cách sử dụng x = threading.Thread(target=thread_function, args=(1,))
x.start()
98
Có một số cách để tránh hoặc giải quyết các điều kiện chủng tộc. Bạn đã giành chiến thắng nhìn vào tất cả chúng ở đây, nhưng có một cặp được sử dụng thường xuyên. Hãy bắt đầu với
Để giải quyết điều kiện cuộc đua của bạn ở trên, bạn cần tìm cách chỉ cho phép một luồng tại một thời điểm vào phần đọc biến đổi-viết trong mã của bạn. Cách phổ biến nhất để làm điều này được gọi là
98 trong Python. Trong một số ngôn ngữ khác, ý tưởng tương tự này được gọi là
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
01. Mutex xuất phát từ loại trừ lẫn nhau, đó chính xác là những gì
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
07 và
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08. Một chủ đề sẽ gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
09 để lấy khóa. Nếu khóa đã được giữ, luồng gọi sẽ đợi cho đến khi nó được phát hành. Có một điểm quan trọng ở đây. Nếu một chủ đề nhận được khóa nhưng không bao giờ trả lại, chương trình của bạn sẽ bị kẹt. Bạn sẽ đọc thêm về điều này sau.
Khác với việc thêm một loạt các bản ghi nhật ký để bạn có thể thấy việc khóa rõ ràng hơn, thay đổi lớn ở đây là thêm một thành viên có tên là
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
15, đó là một đối tượng
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
16.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
15 này được khởi tạo ở trạng thái không khóa và bị khóa và phát hành bởi tuyên bố
Nhìn kìa. Chương trình của bạn cuối cùng cũng hoạt động!
Bạn có thể bật đăng nhập đầy đủ bằng cách đặt cấp độ thành
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
21 bằng cách thêm câu lệnh này sau khi bạn định cấu hình đầu ra ghi nhật ký trong
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
Nhiều ví dụ trong phần còn lại của bài viết này sẽ có mức ghi nhật ký
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
29 và
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
21. Chúng tôi thường chỉ hiển thị đầu ra cấp
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
29, vì nhật ký
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
21 có thể khá dài. Hãy thử các chương trình với việc đăng nhập bật lên và xem những gì họ làm.
Bế tắc
Trước khi bạn tiếp tục, bạn nên xem xét một vấn đề phổ biến khi sử dụng
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
98 được phát hành. Trong ví dụ này, bạn có thể sửa chữa bế tắc bằng cách xóa cuộc gọi thứ hai, nhưng bế tắc thường xảy ra từ một trong hai điều tinh tế:
98 làm người quản lý bối cảnh làm giảm đáng kể mức độ thường xuyên. Bạn nên viết mã bất cứ khi nào có thể sử dụng các nhà quản lý ngữ cảnh, vì họ giúp tránh các tình huống trong đó một ngoại lệ bỏ qua cuộc gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08.
Vấn đề thiết kế có thể là một chút khó khăn hơn trong một số ngôn ngữ. Rất may, luồng Python có một đối tượng thứ hai, được gọi là
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
44, được thiết kế cho tình huống này. Nó cho phép một luồng đến
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
07
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
44 nhiều lần trước khi gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08. Chủ đề đó vẫn được yêu cầu gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08 cùng một số lần được gọi là
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
44 là hai trong số các công cụ cơ bản được sử dụng trong lập trình ren để ngăn chặn các điều kiện chủng tộc. Có một vài hoạt động khác theo những cách khác nhau. Trước khi bạn nhìn vào chúng, hãy để Lừa chuyển sang một miền vấn đề hơi khác.
Nhà sản xuất-người tiêu dùng
Vấn đề của nhà sản xuất là một vấn đề khoa học máy tính tiêu chuẩn được sử dụng để xem xét các vấn đề đồng bộ hóa luồng hoặc quy trình. Bạn sẽ xem xét một biến thể của nó để có được một số ý tưởng về những gì nguyên thủy mà mô -đun Python
Trong ví dụ này, bạn sẽ tưởng tượng một chương trình cần đọc tin nhắn từ mạng và viết chúng vào đĩa. Chương trình không yêu cầu một tin nhắn khi nó muốn. Nó phải được lắng nghe và chấp nhận tin nhắn khi chúng đến. Các tin nhắn sẽ không đến với tốc độ thường xuyên, nhưng sẽ đến trong các vụ nổ. Phần này của chương trình được gọi là nhà sản xuất.
Mặt khác, một khi bạn có một tin nhắn, bạn cần viết nó vào cơ sở dữ liệu. Truy cập cơ sở dữ liệu chậm, nhưng đủ nhanh để theo kịp tốc độ trung bình của tin nhắn. Nó không đủ nhanh để theo kịp khi một loạt các tin nhắn xuất hiện. Phần này là người tiêu dùng.
Ở giữa nhà sản xuất và người tiêu dùng, bạn sẽ tạo ra một
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
53 sẽ là phần thay đổi khi bạn tìm hiểu về các đối tượng đồng bộ hóa khác nhau.
Đó là bố cục cơ bản. Hãy cùng nhìn vào một giải pháp bằng cách sử dụng
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 đọc từ mạng giả và đặt thông điệp vào một
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 có số ngẫu nhiên từ một đến một trăm. Nó gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
62 trên
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63 để gửi nó đến
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 cũng sử dụng giá trị
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
66 để báo hiệu cho người tiêu dùng dừng sau khi nó đã gửi mười giá trị. Đây là một chút khó xử, nhưng đừng lo lắng, bạn sẽ thấy cách để thoát khỏi giá trị
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
66 này sau khi bạn làm việc trong ví dụ này.
Ở phía bên kia của
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63 là người tiêu dùng:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
0
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 đọc một thông báo từ
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63 và ghi nó vào cơ sở dữ liệu giả, trong trường hợp này chỉ đang in nó vào màn hình. Nếu nó nhận được giá trị
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
66, nó sẽ trả về từ hàm, sẽ chấm dứt luồng.
Trước khi bạn nhìn vào phần thực sự thú vị,
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
53, ở đây, phần
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
3, tạo ra các chủ đề này:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
1
Điều này trông khá quen thuộc vì nó gần với mã
$ ./multiple_threads.py
Main : create and start thread 0.Thread 0: startingMain : create and start thread 1.Thread 1: startingMain : create and start thread 2.Thread 2: startingMain : before joining thread 0.Thread 2: finishingThread 1: finishingThread 0: finishingMain : thread 0 doneMain : before joining thread 1.Main : thread 1 doneMain : before joining thread 2.Main : thread 2 done
3 trong các ví dụ trước.
Hãy nhớ rằng bạn có thể bật
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
21 đăng nhập để xem tất cả các tin nhắn ghi nhật ký bằng cách không đưa ra dòng này:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
2
Có thể đáng để đi qua các tin nhắn đăng nhập
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
21 để xem chính xác nơi mỗi luồng thu và phát hành các khóa.
Bây giờ, hãy để Lừa xem
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
53 chuyển tin nhắn từ
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 đến
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
3
Woah! Đó là rất nhiều mã. Một tỷ lệ phần trăm khá cao trong số đó chỉ là các câu lệnh ghi nhật ký để giúp dễ dàng nhìn thấy những gì xảy ra khi bạn chạy nó. Ở đây, cùng một mã với tất cả các câu lệnh ghi nhật ký đã bị xóa:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
4
Điều đó có vẻ dễ quản lý hơn một chút.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
53 trong phiên bản mã của bạn có ba thành viên:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
81 lưu trữ thông điệp để vượt qua. stores the message to pass.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
82 là đối tượng
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
83 hạn chế quyền truy cập vào thông báo bằng chuỗi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59. is a
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
83 object that restricts access to the message by
the
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 thread.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
85 cũng là một
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
83 hạn chế quyền truy cập vào thông báo bằng chuỗi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64. is also a
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
83 that restricts access to the message by the
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 thread.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
88 khởi tạo ba thành viên này và sau đó gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
07 trên
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
85. Đây là trạng thái bạn muốn bắt đầu.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 được phép thêm một tin nhắn mới, nhưng
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 cần phải đợi cho đến khi có tin nhắn.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
93 và
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
94 gần như đối lập.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
93 gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
07 trên
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
97. Đây là cuộc gọi sẽ làm cho
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 đợi cho đến khi một tin nhắn đã sẵn sàng.
Khi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 đã có được
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
85, nó sẽ sao chép giá trị trong
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
81 và sau đó gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08 trên
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
82. Phát hành khóa này là những gì cho phép
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 chèn tin nhắn tiếp theo vào
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63.
Trước khi bạn tiếp tục
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
62, có một điều gì đó tinh tế đang diễn ra trong
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
09. Xem nếu bạn có thể tìm ra lý do tại sao bạn không muốn làm điều đó trước khi tiếp tục.
Ở đây, câu trả lời. Ngay khi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 có thể bắt đầu chạy. Điều đó có thể xảy ra trước khi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08 trở lại! Điều này có nghĩa là có một khả năng nhỏ là khi hàm trả về
14, đó thực sự có thể là thông báo tiếp theo được tạo, vì vậy bạn sẽ mất tin nhắn đầu tiên. Đây là một ví dụ khác về một điều kiện chủng tộc.
Chuyển sang
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
62, bạn có thể thấy phía đối diện của giao dịch.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 sẽ gọi cái này với một tin nhắn. Nó sẽ có được
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
82, đặt
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
81 và cuộc gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08 vào lúc đó
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
97, sẽ cho phép
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 đọc giá trị đó.
Hãy cùng chạy mã có bộ ghi nhật ký thành
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
29 và xem nó trông như thế nào:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
5
Lúc đầu, bạn có thể thấy kỳ lạ là nhà sản xuất nhận được hai tin nhắn trước khi người tiêu dùng chạy. Nếu bạn nhìn lại
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 và
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
62, bạn sẽ nhận thấy rằng nơi duy nhất nó sẽ chờ đợi
98 là khi nó cố gắng đưa tin nhắn vào đường ống. Điều này được thực hiện sau khi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 nhận được tin nhắn và nhật ký rằng nó có nó.
Khi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 cố gắng gửi tin nhắn thứ hai này, nó sẽ gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
62 lần thứ hai và nó sẽ chặn.
Hệ điều hành có thể hoán đổi các luồng bất cứ lúc nào, nhưng thường cho phép mỗi luồng có một lượng thời gian hợp lý để chạy trước khi hoán đổi nó. Đó là lý do tại sao
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 thường chạy cho đến khi nó chặn trong cuộc gọi thứ hai đến
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
62.
Tuy nhiên, khi một luồng bị chặn, hệ điều hành sẽ luôn trao đổi nó và tìm một luồng khác để chạy. Trong trường hợp này, chủ đề duy nhất khác có bất cứ điều gì để làm là
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
93, đọc tin nhắn và gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08 trên
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
82, do đó cho phép
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 chạy lại vào thời gian tiếp theo được hoán đổi.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 đã đọc, mặc dù
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
Mặc dù nó hoạt động cho thử nghiệm hạn chế này, nhưng nó không phải là một giải pháp tuyệt vời cho vấn đề người tiêu dùng sản xuất nói chung vì nó chỉ cho phép một giá trị duy nhất trong đường ống tại một thời điểm. Khi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 nhận được một tin nhắn, nó sẽ không có nơi nào để đặt chúng.
Hãy cùng chuyển sang một cách tốt hơn để giải quyết vấn đề này, sử dụng
Người sản xuất người tiêu dùng sử dụng x = threading.Thread(target=thread_function, args=(1,), daemon=True)
42
Nếu bạn muốn có thể xử lý nhiều hơn một giá trị trong đường ống tại một thời điểm, bạn sẽ cần một cấu trúc dữ liệu cho đường ống cho phép số phát triển và thu nhỏ khi dữ liệu tăng từ
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
54 xảy ra. Việc sử dụng chính trong mã này là các luồng đang chờ sự kiện không nhất thiết phải dừng những gì họ đang làm, họ chỉ có thể kiểm tra trạng thái của
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
6
Những thay đổi duy nhất ở đây là việc tạo đối tượng
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 cũng không phải thay đổi quá nhiều:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
7
Bây giờ nó sẽ lặp lại cho đến khi nó thấy rằng sự kiện được đặt trên dòng 3. Nó cũng không còn đặt giá trị
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
66 vào
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 đã phải thay đổi thêm một chút:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
8
Mặc dù bạn phải lấy mã liên quan đến giá trị
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
54 được đặt, mà còn cần phải tiếp tục lặp cho đến khi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63 được làm trống.
Đảm bảo hàng đợi trống trước khi người tiêu dùng hoàn thành ngăn chặn một vấn đề thú vị khác. Nếu
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 thoát ra trong khi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63 có tin nhắn trong đó, có hai điều xấu có thể xảy ra. Đầu tiên là bạn mất những tin nhắn cuối cùng đó, nhưng điều nghiêm trọng hơn là
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 có thể bị bắt khi cố gắng thêm một tin nhắn vào hàng đợi đầy đủ và không bao giờ quay lại.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
Nếu điều đó xảy ra, người tiêu dùng có thể thức dậy và thoát ra với hàng đợi vẫn hoàn toàn đầy đủ.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 sau đó sẽ gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
62 sẽ đợi cho đến khi có không gian trên hàng đợi cho tin nhắn mới.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 đã thoát ra, vì vậy điều này sẽ không xảy ra và
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 sẽ không thoát ra.
Phần còn lại của
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 sẽ trông quen thuộc.
Tuy nhiên,
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
53 đã thay đổi đáng kể:
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
9
Bạn có thể thấy rằng
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
85, thì hàng đợi sẽ phát triển đến giới hạn bộ nhớ máy tính của bạn.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
93 và
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
Nếu bạn đọc qua đầu ra trong ví dụ của tôi, bạn có thể thấy một số điều thú vị xảy ra. Ngay trên đỉnh, bạn có thể thấy
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 phải tạo năm tin nhắn và đặt bốn trong số chúng vào hàng đợi. Nó đã được trao đổi bởi hệ điều hành trước khi nó có thể đặt cái thứ năm.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 sau đó chạy và rút tin nhắn đầu tiên. Nó đã in ra thông điệp đó cũng như hàng đợi sâu như thế nào vào thời điểm đó:
Đây là cách bạn biết rằng tin nhắn thứ năm đã không đưa nó vào
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63. Hàng đợi giảm xuống kích thước ba sau khi một tin nhắn được xóa. Bạn cũng biết rằng
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 thoát ra ngay lập tức.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 vẫn có một loạt các công việc làm, vì vậy nó tiếp tục chạy cho đến khi nó đã làm sạch
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
63.
Hãy thử chơi với các kích thước hàng đợi khác nhau và các cuộc gọi đến
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
59 hoặc
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
64 để mô phỏng thời gian truy cập mạng hoặc đĩa dài hơn tương ứng. Ngay cả những thay đổi nhỏ đối với các yếu tố này của chương trình sẽ tạo ra sự khác biệt lớn trong kết quả của bạn.
Đây là một giải pháp tốt hơn nhiều cho vấn đề người tiêu dùng sản xuất, nhưng bạn có thể đơn giản hóa nó nhiều hơn.
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
53 thực sự không cần thiết cho vấn đề này. Khi bạn lấy đi việc khai thác, nó sẽ trở thành
42 là các lớp tiện dụng để giải quyết các vấn đề đồng thời, nhưng có những lớp khác được cung cấp bởi thư viện tiêu chuẩn. Trước khi bạn kết thúc hướng dẫn này, hãy để một cuộc khảo sát nhanh về một số trong số họ.
Đối tượng luồng
Có một vài nguyên thủy được cung cấp bởi mô -đun Python
5. Mặc dù bạn đã không cần những điều này cho các ví dụ ở trên, nhưng chúng có thể có ích trong các trường hợp sử dụng khác nhau, vì vậy, thật tốt khi làm quen với chúng.
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
17. A
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
18 là một bộ đếm với một vài thuộc tính đặc biệt. Điều đầu tiên là việc đếm là nguyên tử. Điều này có nghĩa là có một đảm bảo rằng hệ điều hành sẽ không trao đổi luồng ở giữa tăng hoặc giảm bộ đếm.
Bộ đếm bên trong được tăng lên khi bạn gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08 và giảm khi bạn gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
07.
Thuộc tính đặc biệt tiếp theo là nếu một luồng gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
07 khi bộ đếm bằng không, luồng đó sẽ chặn cho đến khi một luồng khác gọi
$ ./single_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all doneThread 1: finishing
08 và tăng bộ đếm lên một.
Semaphores thường được sử dụng để bảo vệ một tài nguyên có công suất hạn chế. Một ví dụ sẽ là nếu bạn có một nhóm kết nối và muốn giới hạn kích thước của nhóm đó ở một số cụ thể.
Hẹn giờ
Một
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
23 là một cách để lên lịch cho một chức năng được gọi sau một khoảng thời gian nhất định đã trôi qua. Bạn tạo một
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
24 bằng cách chuyển trong một số giây để chờ đợi và một chức năng để gọi:
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
24 bằng cách gọi
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
4. Hàm sẽ được gọi trên một chủ đề mới tại một số điểm sau thời gian được chỉ định, nhưng lưu ý rằng không có lời hứa rằng nó sẽ được gọi chính xác tại thời điểm bạn muốn.
Nếu bạn muốn dừng một
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
24 mà bạn đã bắt đầu, bạn có thể hủy nó bằng cách gọi
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
28. Gọi
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
28 sau khi
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
24 đã kích hoạt không làm gì và không tạo ra một ngoại lệ.
Một
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
24 có thể được sử dụng để nhắc người dùng hành động sau một khoảng thời gian cụ thể. Nếu người dùng thực hiện hành động trước khi
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
24 hết hạn,
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
28 có thể được gọi.
Rào chắn
Một
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
34 có thể được sử dụng để giữ một số lượng cố định các luồng đồng bộ. Khi tạo
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
35, người gọi phải chỉ định số lượng chủ đề sẽ được đồng bộ hóa trên nó. Mỗi luồng gọi
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
36 trên
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
35. Tất cả chúng sẽ vẫn bị chặn cho đến khi số lượng luồng được chỉ định đang chờ, và sau đó tất cả được phát hành cùng một lúc.
Hãy nhớ rằng các chủ đề được lên lịch bởi hệ điều hành, mặc dù tất cả các luồng được phát hành đồng thời, chúng sẽ được lên kế hoạch để chạy một lần.
Một cách sử dụng cho
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
35 là cho phép một nhóm các luồng tự khởi tạo. Có các luồng chờ trên
$ ./daemon_thread.py
Main : before creating threadMain : before running threadThread 1: startingMain : wait for the thread to finishMain : all done
35 sau khi chúng được khởi tạo sẽ đảm bảo rằng không có luồng nào bắt đầu chạy trước khi tất cả các luồng được hoàn thành với khởi tạo của chúng.
5 cung cấp và một số ví dụ về cách xây dựng các chương trình có luồng và các vấn đề họ giải quyết. Bạn cũng đã thấy một vài trường hợp về các vấn đề phát sinh khi viết và gỡ lỗi các chương trình có chủ đề.
Nếu bạn muốn khám phá các tùy chọn khác cho sự đồng thời trong Python, hãy kiểm tra tăng tốc chương trình Python của bạn với sự đồng thời.
Nếu bạn quan tâm đến việc thực hiện một lần lặn sâu trên mô -đun
7, hãy đọc Async IO trong Python: một hướng dẫn hoàn chỉnh.
Dù bạn làm gì, bây giờ bạn có thông tin và sự tự tin mà bạn cần viết các chương trình bằng cách sử dụng chuỗi Python!
Đặc biệt cảm ơn độc giả JL Diaz vì đã giúp dọn dẹp phần giới thiệu.
Xem bây giờ hướng dẫn này có một khóa học video liên quan được tạo bởi nhóm Python thực sự. Xem nó cùng với hướng dẫn bằng văn bản để làm sâu sắc thêm sự hiểu biết của bạn: Xây dựng bằng Python This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Threading in Python
Chủ đề trong Python với ví dụ là gì?
Một chủ đề là gì?Một luồng là một luồng thực thi riêng biệt.Điều này có nghĩa là chương trình của bạn sẽ có hai điều xảy ra cùng một lúc.Nhưng đối với hầu hết các triển khai Python 3, các chủ đề khác nhau không thực sự thực hiện cùng một lúc: chúng chỉ xuất hiện.a separate flow of execution. This means that your program will have two things happening at once. But for most Python 3 implementations the different threads do not actually execute at the same time: they merely appear to.
Làm thế nào các chủ đề được sử dụng trong Python?
Các chủ đề Python được sử dụng trong trường hợp việc thực hiện một nhiệm vụ liên quan đến một số chờ đợi.Một ví dụ sẽ là tương tác với một dịch vụ được lưu trữ trên một máy tính khác, chẳng hạn như máy chủ web.Chủ đề cho phép Python thực thi mã khác trong khi chờ đợi;Điều này dễ dàng mô phỏng với chức năng giấc ngủ.in cases where the execution of a task involves some waiting. One example would be interaction with a service hosted on another computer, such as a webserver. Threading allows python to execute other code while waiting; this is easily simulated with the sleep function.
Những cách khác để tạo chủ đề trong Python là gì?
Có hai cách tạo ra các chủ đề trong Python và đó là;sử dụng một lớp hoặc sử dụng một chức năng.using a class or using a function.