Giá trị trả về của bộ đếm thời gian luồng python

Phân luồng Python cho phép bạn chạy đồng thời các phần khác nhau của chương trình 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ác luồng, thì hướng dẫn này là dành cho bạn

Trong bài viết này, bạn sẽ học

  • chủ đề là gì
  • Cách tạo chủ đề và đợi chúng hoàn thành
  • Cách sử dụng
    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    5
  • Làm thế nào để tránh điều kiện chủng tộc
  • Cách sử dụng các công cụ phổ biến mà Python
    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    6 cung cấp

Bài viết này giả định rằng bạn đã có kiến ​​thức cơ bản về Python và bạn đang sử dụng ít nhất phiên bản 3. 6 để chạy các ví dụ. Nếu cần ôn lại, bạn có thể bắt đầu với Lộ trình học Python và bắt kịp tốc độ

Nếu bạn không chắc mình muốn sử dụng Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6,
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
8 hay
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
9, thì bạn có thể xem bài viết Tăng tốc chương trình Python của bạn với tính đồ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 Real Python GitHub

Tiền thưởng miễn phí. 5 Suy nghĩ về Làm chủ Python, một khóa học miễn phí dành cho các nhà phát triển Python cho bạn thấy lộ trình và tư duy mà bạn sẽ cần để đưa các kỹ năng Python của mình lên một tầm cao mới

Lấy bài kiểm tra. Kiểm tra kiến ​​thức của bạn với bài kiểm tra tương tác “Python Threading” của chúng tôi. Sau khi hoàn thành, bạn sẽ nhận được điểm số để có thể theo dõi quá trình học tập của mình theo thời gian

Lấy bài kiểm tra "

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 việc 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 luồng khác nhau không thực sự thực thi cùng một lúc. họ chỉ xuất hiện để

Thật hấp dẫn khi nghĩ về phân luồng giống như có hai (hoặc nhiều) bộ xử lý khác nhau chạy trên chương trình của bạn, mỗi bộ xử lý thực hiện một nhiệm vụ độc lập cùng một lúc. Điều đó gần đú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 luồng tại một thời điểm

Để nhiều tác 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 ngôn ngữ khác hoặc sử dụng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
9 đi kèm với một số chi phí bổ sung

Do cách triển khai CPython của Python hoạt động, luồng có thể không tăng tốc tất cả các tác 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 chuỗi Python chạy tại một thời điểm

Các tác vụ dành nhiều thời gian chờ đợi các sự kiện bên ngoài thường là những ứng cử viên tốt cho luồng. Các sự cố 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

Đ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 cả tài liệu để xem nó xử lý các luồng như thế nào

Nếu bạn đang chạy triển khai Python tiêu chuẩn, chỉ viết bằng Python và gặp sự cố liên quan đến CPU, bạn nên kiểm tra mô-đun

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
9 để thay thế

Kiến trúc chương trình của bạn để sử dụng phân luồng cũng có thể mang lại sự rõ ràng trong thiết kế. Hầu hết các ví dụ bạn sẽ tìm hiểu trong hướng dẫn này không nhất thiết phải chạy nhanh hơn vì chúng sử dụng các luồng. Sử dụng luồng trong chúng giúp làm cho thiết kế sạch hơn và dễ suy luận hơn

Vì vậy, hãy ngừng nói về luồng và bắt đầu sử dụng nó

Loại bỏ các quảng cáo

Bắt đầu một chủ đề

Bây giờ bạn đã có ý tưởng về chủ đề là gì, hãy tìm hiểu cách tạo một chủ đề. Thư viện chuẩn của Python cung cấp

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6, chứa hầu hết các nguyên hàm mà bạn sẽ thấy trong bài viết này.
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
3, trong mô-đun này, gói gọn các luồng một cách độc đáo, cung cấp giao diện rõ ràng để làm việc với chúng

Để bắt đầu một chuỗi riêng biệt, bạn tạo một phiên bản

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
3 và sau đó yêu cầu nó cho
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
5

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
1

Nếu bạn nhìn xung quanh các câu lệnh ghi nhật ký, bạn có thể thấy rằng phần

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
6 đang tạo và bắt đầu chuỗi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
3

Khi bạn tạo một

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
3, bạn truyền cho nó một hàm và một danh sách chứa các đối số của hàm đó. Trong trường hợp này, bạn đang yêu cầu
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
3 chạy
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
9 và chuyển nó
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
70 làm đối số

Đối với bài viết này, bạn sẽ sử dụng các số nguyên tuần tự làm tên cho chủ đề của mình. Có

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
71, trả về một tên duy nhất cho mỗi luồng, nhưng những tên này thường không ngắn và cũng không dễ đọc

________ 89 bản thân nó không làm được gì nhiều. Nó chỉ đơn giản là ghi lại một số tin nhắn với một

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
73 ở giữa chúng

Khi bạn chạy chương trình này như hiện tại (với dòng 20 được chú thích), đầu ra sẽ như thế này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
1

Bạn sẽ nhận thấy rằng phần

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
3 đã hoàn thành sau phần
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
75 trong mã của bạn. Bạn sẽ quay lại với lý do tại sao lại như vậy và nói về dòng số 20 bí ẩn trong phần tiếp theo

chủ đề daemon

Trong khoa học máy tính, một

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
76 là một quá trình chạy trong nền

Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6 có ý nghĩa cụ thể hơn đối với
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
76. Chuỗi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
76 sẽ tắt ngay lập tức khi thoát khỏi chương trình. Một cách để suy nghĩ về những định nghĩa này là coi luồng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
76 là luồng chạy trong nền mà không cần lo lắng về việc tắt nó

Nếu một chương trình đang chạy

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
91 mà không phải là
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
92, thì chương trình sẽ đợi các luồng đó hoàn thành trước khi kết thúc. Tuy nhiên,
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
91 là daemon, chỉ bị giết ở bất cứ đâu khi chương trình đang thoát

Hãy xem xét kỹ hơn một chút đầu ra của chương trình của bạn ở trên. Hai dòng cuối cùng là một chút 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

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
94 đã in thông báo
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
95 của nó và trước khi luồng kết thúc

Việc tạm dừng này là Python đang chờ chuỗi không phải daemon hoàn thành. Khi chương trình Python của bạn kết thúc, một phần của quy trình tắt máy là dọn sạch quy trình luồng

Nếu bạn xem nguồn của Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6, bạn sẽ thấy rằng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
97 duyệt qua tất cả các luồng đang chạy và gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
98 trên mọi luồng không có cờ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
76 được đặt

Vì vậy, chương trình của bạn chờ để thoát vì bản thân luồng đang chờ trong chế độ ngủ. Ngay sau khi hoàn thành và in thông báo,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
98 sẽ quay trở lại và chương trình có thể thoát

Thông thường, hành vi này là những gì bạn muốn, nhưng có các tùy chọn khác có sẵn cho chúng tôi. Trước tiên, hãy lặp lại chương trình với luồng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
76. 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 thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
3, thêm cờ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
13

x = threading.Thread(target=thread_function, args=(1,), daemon=True)

Khi bạn chạy chương trình bây giờ, bạn sẽ thấy đầu ra này

________số 8

Sự khác biệt ở đây là dòng cuối cùng của đầu ra bị thiếu.

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
9 không có cơ hội hoàn thành. Đó là một chuỗi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
76, vì vậy khi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
94 đến cuối mã của nó và chương trình muốn kết thúc, daemon đã bị giết

Loại bỏ các quảng cáo

x = threading.Thread(target=thread_function, args=(1,), daemon=True) 17 một chủ đề

Chủ đề daemon rất tiện dụng, nhưng còn khi bạn muốn đợi một chủ đề dừng lại thì sao?

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
7

Để yêu cầu một luồng chờ một luồng khác kết thúc, bạn gọi ____298. Nếu bạn bỏ ghi chú dòng đó, luồng chính sẽ tạm dừng và đợi luồng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
19 chạy hoàn tất

Bạn đã kiểm tra điều này trên mã bằng luồng daemon hay luồng thông thường chưa? . Nếu bạn

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
98 một chủ đề, câu lệnh đó sẽ đợi cho đến khi một trong hai loại chủ đề kết thúc

Làm việc với nhiều chủ đề

Mã ví dụ cho đến nay chỉ hoạt động với hai luồng. chủ đề chính và một chủ đề mà bạn đã bắt đầu với đối tượng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
21

Thông thường, bạn sẽ muốn bắt đầu một số chủ đề và để chúng thực hiện công việc thú vị. Hãy bắt đầu bằng cách xem xét cách khó hơn để thực hiện điều đó, sau đó bạn sẽ chuyển sang phương pháp dễ dàng hơn

Cách khó hơn để bắt đầu nhiều chủ đề là cách bạn đã biết

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
9

Mã này sử dụng cùng một cơ chế mà bạn đã thấy ở trên để bắt đầu chuỗi, tạo đối tượng

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
3, sau đó gọi
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
5. Chương trình giữ một danh sách các đối tượng
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
3 để có thể đợi chúng sau này bằng cách sử dụng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
98

Chạy mã này nhiều lần sẽ có thể tạo ra một số kết quả thú vị. Đây là một ví dụ đầu ra từ máy của tôi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
1

Nếu xem kỹ đầu ra, bạn sẽ thấy cả ba luồng 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 thông báo

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
26 để cho bạn biết khi mỗi chủ đề hoàn thành

Thứ tự chạy các luồng được xác định bởi hệ điều hành và có thể khá khó dự đoán. Nó có thể (và có khả năng sẽ) khác nhau giữa các lần chạy, vì vậy bạn cần lưu ý điều đó khi thiết kế các thuật toán sử dụng phân luồng

May mắn thay, Python cung cấp cho bạn một số nguyên hàm 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 xem cách quản lý một nhóm chủ đề dễ dàng hơn một chút

Sử dụng một x = threading.Thread(target=thread_function, args=(1,), daemon=True) 5

Có một cách dễ dàng hơn để bắt đầu một nhóm chủ đề so với cách bạn đã thấy ở trên. Nó được gọi là

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5 và là một phần của thư viện chuẩn trong
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
29 (kể từ Python 3. 2)

Cách dễ nhất để tạo nó là với tư cách là người quản lý bối cảnh, sử dụng câu lệnh

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
30 để quản lý việc tạo và hủy nhóm

Đây là

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
94 từ ví dụ cuối cùng được viết lại để sử dụng một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
2

Mã này tạo một

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5 với tư cách là người quản lý bối cảnh, cho nó biết có bao nhiêu luồng công nhân mà nó muốn trong nhóm. Sau đó, nó sử dụng ____634 để chuyển qua một thứ có thể lặp lại, trong trường hợp của bạn là ____635, chuyển từng thứ tới một luồng trong nhóm

Sự kết thúc của khối

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
30 làm cho
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5 thực hiện một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
98 trên mỗi luồng trong nhóm. Chúng tôi thực sự khuyên bạn nên sử dụng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5 làm trình quản lý bối cảnh khi có thể để bạn không bao giờ quên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
98 chủ đề

Ghi chú. Sử dụng một

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5 có thể gây ra một số lỗi khó hiểu

Ví dụ: nếu bạn gọi một hàm không có tham số, nhưng bạn truyền tham số cho nó trong

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
34, luồng sẽ đưa ra một ngoại lệ

Thật không may,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5 sẽ ẩn ngoại lệ đó và (trong trường hợp trên) chương trình kết thúc mà không có đầu ra. Điều này có thể khá khó hiểu khi gỡ lỗi lúc đầu

Chạy mã ví dụ đã sửa của bạn sẽ tạo ra kết quả giống như thế này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
3

Một lần nữa, hãy chú ý cách

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 hoàn thành trước
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
305. Việc lập lịch trình cho các luồng được thực hiện bởi hệ điều hành và không tuân theo một kế hoạch dễ hình dung

Loại bỏ các quảng cáo

Điều kiện cuộc đua

Trước khi bạn chuyển sang một số tính năng khác có trong Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6, hãy 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 theo luồng. điều kiện cuộc đua

Sau khi bạn đã biết điều kiện chủng tộc là gì và xem xét điều kiện xảy ra, bạn sẽ chuyển sang một số nguyên tắc do thư viện tiêu chuẩn cung cấp để ngăn chặn điều kiện chủng tộc xảy ra

Điều kiện cạnh tranh có thể xảy ra khi hai hoặc nhiều luồng truy cập vào 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 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 cuộc đua không rõ ràng như vậy. Thông thường, chúng chỉ hiếm khi xảy ra 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 khiến 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úc và bạn sẽ xem chi tiết về nó để giải thích điều 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 sẽ không thực sự có một cơ sở dữ liệu. bạn sẽ giả mạo nó, bởi vì đó không phải là điểm của bài viết này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
307 của bạn sẽ có các phương thức
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
308 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
30

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
307 đang theo dõi một số duy nhất.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
311. Đây sẽ là dữ liệu được chia sẻ mà bạn sẽ thấy điều kiện cuộc đua

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
308 chỉ cần khởi tạo
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
311 thành 0. Càng xa càng tốt

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309 nhìn hơi lạ. Nó đang 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 đó và sau đó ghi 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

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
311 vào một biến cục bộ. Việc tính toán chỉ là thêm một vào giá trị và sau đó
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
316 một chút. Cuối cùng, nó ghi lại giá trị bằng cách sao chép giá trị cục bộ trở lại
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
311

Đây là cách bạn sẽ sử dụng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
307 này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
31

Chương trình tạo một

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5 với hai luồng và sau đó gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
320 trên mỗi luồng, yêu cầu chúng chạy
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
321

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
320 có một chữ ký cho phép cả đối số vị trí và đối số được đặt tên được chuyển đến hàm đang chạy trong luồng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
32

Trong cách sử dụng ở trên,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
323 được chuyển làm đối số vị trí đầu tiên và duy nhất cho
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
321. Bạn sẽ thấy ở phần sau của bài viết này, nơi bạn có thể truyền nhiều đối số theo cách tương tự

Vì mỗi chuỗi chạy

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309 thêm một chuỗi vào
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
311, bạn có thể mong đợi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
328 là
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
329 khi nó được in ra ở cuối. Nhưng bạn sẽ không xem xét ví dụ này nếu đó là trường hợp. Nếu bạn chạy đoạn mã trên, kết quả sẽ như thế này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
33

Bạn có thể đã mong đợi điều đó xảy ra, nhưng hãy xem chi tiết về những gì đang thực sự xảy ra ở đây, vì điều đó sẽ giúp giải pháp cho vấn đề này dễ hiểu hơn

Loại bỏ các quảng cáo

một chủ đề

Trước khi bạn đi sâu vào vấn đề này với hai luồng, hãy quay lại và nói một chút về một số chi tiết về cách hoạt động của các luồng

Bạn sẽ không đ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 số thứ theo cách không chính xác về mặt kỹ thuật nhưng sẽ cung cấp cho bạn ý tưởng đúng về những gì đang xảy ra

Khi bạn yêu cầu

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
5 của mình chạy từng luồng, bạn sẽ cho nó biết chức năng nào sẽ chạy và những tham số nào cần truyền cho nó.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
331

Kết quả của việc này là mỗi luồng trong nhóm sẽ gọi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
332. Lưu ý rằng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
333 là tham chiếu đến một đối tượng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
307 được tạo trong
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
94. Gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309 trên đối tượng đó gọi một phương thức thể hiện trên đối tượng đó

Mỗi luồng sẽ có một tham chiếu đến cùng một đối tượng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
307,
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
333. Mỗi luồng cũng sẽ có một giá trị duy nhất,
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
323, để làm cho các báo cáo ghi nhật ký dễ đọc hơn một chút

Giá trị trả về của bộ đếm thời gian luồng python

Khi luồng bắt đầu chạy

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309, nó có phiên bản riêng của tất cả dữ liệu cục bộ cho hàm. Trong trường hợp của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309, đây là
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342. Đâ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. Điều đó có nghĩa là tất cả các biến nằm trong phạm vi (hoặc cục bộ) của một hàm đều an toàn cho luồng

Bây giờ bạn có thể bắt đầu tìm hiểu xem điều gì sẽ xảy ra nếu bạn chạy chương trình ở trên với một chuỗi và một lệnh gọi duy nhất tới

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309

Hình ảnh dưới đây hướng dẫn thực thi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309 nếu chỉ có một luồng duy nhất được chạy. Câu lệnh được hiển thị ở bên trái, theo sau là sơ đồ hiển thị các giá trị trong
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342 của luồng và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
328 được chia sẻ

Giá trị trả về của bộ đếm thời gian luồng python

Sơ đồ được bố trí sao cho thời gian tăng lên khi bạn di chuyển từ trên xuống dưới. Nó bắt đầu khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 được tạo và kết thúc khi nó bị chấm dứt

Khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 bắt đầu,
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
349 bằng không. Dòng mã đầu tiên trong phương thức,
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
350, sao chép giá trị 0 vào biến cục bộ. Tiếp theo, nó tăng giá trị của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342 bằng câu lệnh
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
352. Bạn có thể thấy
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
311 trong
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 được đặt thành một

Tiếp theo,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
73 được gọi, làm cho luồng hiện tại tạm dừng và cho phép các luồng khác chạy. Vì chỉ có một luồng trong ví dụ này nên điều này không có hiệu lực

Khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 thức dậy và tiếp tục, nó sẽ sao chép giá trị mới từ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342 sang
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
349, sau đó chuỗi hoàn thành. Bạn có thể thấy rằng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
328 được đặt thành một

Càng xa càng tốt. Bạn đã chạy

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309 một lần và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
349 đã được tăng lên một

hai chủ đề

Quay trở lại điều kiện cuộc đua, hai luồng sẽ chạy đồng thời nhưng không đồng thời. Mỗi người sẽ có phiên bản

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342 của riêng mình và mỗi người sẽ trỏ đến cùng một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
333. Chính đối tượng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
333 được chia sẻ này sẽ gây ra sự cố

Chương trình bắt đầu với

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 đang chạy
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309

Giá trị trả về của bộ đếm thời gian luồng python

Khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
73, nó cho phép luồng khác bắt đầu chạy. Đây là nơi mà mọi thứ trở nên thú vị

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
369 khởi động và thực hiện các thao tác tương tự. Nó cũng đang sao chép
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
328 vào
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342 riêng tư của nó và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
328 được chia sẻ này vẫn chưa được cập nhật

Giá trị trả về của bộ đếm thời gian luồng python

Khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
369 cuối cùng đi vào chế độ ngủ,
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
328 được chia sẻ vẫn chưa được sửa đổi ở mức 0 và cả hai phiên bản riêng của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342 đều có giá trị là một

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 hiện thức dậy và lưu phiên bản
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342 của nó rồi chấm dứt, tạo cơ hội cuối cùng cho
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
369 để chạy.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
369 không biết rằng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 đã chạy và cập nhật
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
328 khi nó đang ngủ. Nó lưu trữ phiên bản
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
342 của nó vào
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
328, đồng thời đặt nó thành một

Giá trị trả về của bộ đếm thời gian luồng python

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 tương tự có thể phát sinh khi một luồng giải phóng bộ nhớ hoặc đóng một bộ xử lý tệp trước khi luồng kia truy cập xong.

Loại bỏ các quảng cáo

Tại sao đây không phải 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 chạy đ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ể hoán đổi một luồng bất kỳ lúc nào, nên có thể ngắt một câu lệnh như

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
384 sau khi nó đã đọc giá trị của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
19 nhưng trước khi nó ghi 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 vui lòng bỏ qua phần ẩn này

Điều này thực sự hoạt động như thế nàoHiển thị/Ẩn

Đoạn mã trên không hoàn toàn có sẵn như ban đầu bạn có thể nghĩ. Nó được thiết kế để bắt buộc một điều kiện cuộc đua mỗi khi bạn chạy nó, nhưng điều đó giúp giải quyết vấn đề dễ dàng hơn nhiều so với hầu hết các điều kiện cuộc đua

Có hai điều cần lưu ý khi nghĩ về điều kiện cuộc đua

  1. Ngay cả một hoạt động như

    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    386 cũng cần 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ý

  2. Hệ điều hành có thể hoán đổi luồng nào đang chạy bất cứ lúc nào. Một chuỗi có thể được hoán đổi sau bất kỳ hướng dẫn nhỏ nào sau đây. Điều này có nghĩa là một luồng có thể được đặt ở chế độ ngủ để cho một luồng khác chạy ở giữa câu lệnh Python

Hãy xem xét điều này một cách chi tiết. REPL bên dưới hiển thị một hàm nhận một tham số và tăng nó

>>>

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
34

Ví dụ REPL sử dụng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
387 từ thư viện chuẩn Python để hiển thị các bước nhỏ hơn mà bộ xử lý thực hiện để thực hiện chức năng của bạn. Nó thực hiện một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
388 của giá trị dữ liệu
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
19, nó thực hiện một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
390 và sau đó nó sử dụng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
391 để cộng các giá trị đó lại với nhau

Chúng tôi dừng lại ở đây vì một lý do cụ thể. Đây là điểm trong

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
309 ở trên nơi mà
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
73 buộc các chủ đề phải chuyển đổi. Hoàn toàn có khả năng, thỉnh thoảng, hệ điều hành sẽ chuyển các luồng tại điểm chính xác đó ngay cả khi không có
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
394, nhưng lệnh gọi tới
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
394 khiến điều đó xảy ra mọi lúc

Như bạn đã tìm hiểu ở trên, hệ điều hành có thể hoán đổi luồng bất kỳ lúc nào. Bạn đã đi xuống danh sách này đến tuyên bố được đánh dấu

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
396. Nếu hệ điều hành hoán đổi luồng này và chạy một luồng khác cũng sửa đổi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
19, thì khi luồng này hoạt động trở lại, nó sẽ ghi đè lên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
19 với một giá trị không chính xác

Về mặt kỹ thuật, ví dụ này sẽ không có điều kiện chạy đua vì

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
19 là cục bộ của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
100. Tuy nhiên, nó minh họa cách một luồng có thể bị gián đoạn trong một thao tác Python. Nhóm hoạt động TẢI, SỬA ĐỔI, CỬA HÀNG tương tự cũng xảy ra trên các giá trị chung và giá trị chung. Bạn có thể khám phá với mô-đun
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
387 và tự mình chứng minh điều đó

Rất hiếm khi xảy ra tình trạng cuộc đua như thế này, nhưng hãy nhớ rằng một sự kiện không thường xuyên xảy ra sau hàng triệu lần lặp lại có khả năng xảy ra. Sự hiếm có của các điều kiện chủng tộc này khiến chúng khó gỡ lỗi hơn nhiều so với các lỗi thông thường.

Bây giờ hãy quay lại phần hướng dẫn được lên lịch thường xuyên của bạn

Bây giờ bạn đã thấy một điều kiện chủng tộc đang hoạt động, hãy cùng tìm hiểu cách giải quyết chúng

Đồng bộ hóa Cơ bản Sử dụng x = threading.Thread(target=thread_function, args=(1,), daemon=True) 102

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 sẽ không xem xét tất cả chúng ở đây, nhưng có một số được sử dụng thường xuyên. Hãy bắt đầu với

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102

Để giải quyết tình trạng tương tranh của bạn ở trên, bạn cần tìm cách chỉ cho phép một chuỗi tại một thời điểm vào phần đọc-sửa-ghi trong mã của bạn. Cách phổ biến nhất để làm điều này được gọi là

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 trong Python. Trong một số ngôn ngữ khác, ý tưởng tương tự này được gọi là
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
105. Mutex đến từ Loại trừ MUTual, đó chính xác là những gì mà
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 làm

Một

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 là một đối tượng hoạt động giống như thẻ hành lang. Chỉ một chủ đề tại một thời điểm có thể có
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102. Bất kỳ chủ đề nào khác muốn có
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 phải đợi cho đến khi chủ sở hữu của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 từ bỏ nó

Các chức năng cơ bản để làm điều này là

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
111 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112. Một chủ đề sẽ gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
113 để lấy khóa. Nếu khóa đã được giữ, chuỗi cuộc gọi sẽ đợi cho đến khi nó được giải phóng. Có một điểm quan trọng ở đây. Nếu một luồng 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

May mắn thay,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 của Python cũng sẽ hoạt động như một trình quản lý bối cảnh, vì vậy bạn có thể sử dụng nó trong câu lệnh
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
30 và nó sẽ tự động được giải phóng khi khối
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
30 thoát ra vì bất kỳ lý do gì

Hãy nhìn vào

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
307 với một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 được thêm vào nó. Chức năng gọi vẫn giữ nguyên

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
35

Ngoài việc thêm một loạt nhật ký gỡ lỗi để bạn có thể thấy 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à

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
119, là một đối tượng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
120.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
119 này được khởi tạo ở trạng thái không khóa và bị khóa và giải phóng bởi câu lệnh
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
30

Điều đáng chú ý ở đây là chuỗi chạy chức năng này sẽ giữ lại

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 đó cho đến khi hoàn tất việc cập nhật cơ sở dữ liệu. Trong trường hợp này, điều đó có nghĩa là nó sẽ giữ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 trong khi sao chép, cập nhật, ngủ và sau đó ghi giá trị trở lại cơ sở dữ liệu

Nếu bạn chạy phiên bản này với ghi nhật ký được đặt ở mức cảnh báo, bạn sẽ thấy điều này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
36

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 ghi nhật ký đầy đủ bằng cách đặt mức thành

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
125 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
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
94

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
37

Chạy chương trình này với tính năng ghi nhật ký

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
125 được bật trông như thế này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
38

Trong kết quả này, bạn có thể thấy

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
305 nhận được khóa và vẫn đang giữ nó khi chuyển sang chế độ ngủ.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 sau đó bắt đầu và cố gắng lấy cùng một khóa. Vì
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
305 vẫn đang giữ nên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
304 phải đợi. Đây là loại trừ lẫn nhau mà một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 cung cấp

Nhiều ví dụ trong phần còn lại của bài viết này sẽ có ghi nhật ký cấp độ

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
133 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
125. Nói chung, chúng tôi sẽ chỉ hiển thị đầu ra ở cấp độ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
133, vì nhật ký của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
125 có thể khá dài. Hãy thử các chương trình có đăng nhập và xem những gì họ làm

Loại bỏ các quảng cáo

Bế tắc

Trước khi tiếp tục, bạn nên xem xét một vấn đề thường gặp khi sử dụng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
137. Như bạn đã thấy, nếu
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 đã được mua, lệnh gọi thứ hai đến
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
111 sẽ đợi cho đến khi chuỗi đang giữ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112. Bạn nghĩ điều gì sẽ xảy ra khi bạn chạy mã này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
39

Khi chương trình gọi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
142 lần thứ hai, nó bị treo chờ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 được giải phóng. Trong ví dụ này, bạn có thể khắc phục bế tắc bằng cách loại bỏ cuộc gọi thứ hai, nhưng bế tắc thường xảy ra từ một trong hai điều nhỏ

  1. Lỗi triển khai trong đó
    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    102 không được phát hành đúng cách
  2. Một vấn đề thiết kế trong đó chức năng tiện ích cần được gọi bởi các chức năng có thể có hoặc chưa có
    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    102

Tình huống đầu tiên đôi khi xảy ra, nhưng việc sử dụng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 làm trình quản lý bối cảnh sẽ giảm đáng kể tần suất. Bạn nên viết mã bất cứ khi nào có thể để sử dụng trình quản lý ngữ cảnh, vì chúng giúp tránh các tình huống mà một ngoại lệ bỏ qua bạn qua lệnh gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112

Vấn đề thiết kế có thể phức tạp hơn một chút ở một số ngôn ngữ. Rất may, luồng Python có một đối tượng thứ hai, được gọi là

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
148, được thiết kế cho tình huống này. Nó cho phép một thread tới
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
111 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
148 nhiều lần trước khi nó gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112. Chủ đề đó vẫn được yêu cầu gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112 bằng số lần nó gọi là
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
111, nhưng dù sao thì nó cũng phải làm như vậy

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
148 là hai trong số các công cụ cơ bản được sử dụng trong lập trình luồng để ngăn chặn điều kiện chủng tộc. Có một số khác hoạt động theo những cách khác nhau. Trước khi bạn xem xét chúng, hãy chuyển sang một lĩnh vực vấn đề hơi khác

Luồng sản xuất-người tiêu dùng

Vấn đề Nhà sản xuất-Người tiêu dùng 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 đề về luồng hoặc quá trình đồng bộ hóa. Bạn sẽ xem xét một biến thể của nó để có một số ý tưởng về những gì nguyên thủy mà mô-đun Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6 cung cấp

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à ghi 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 đang 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 độ đều đặn mà sẽ đến từng đợt. Phần này của chương trình được gọi là nhà sản xuất

Mặt khác, khi bạn có một tin nhắn, bạn cần ghi 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 tin nhắn đến. Bộ 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 một

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157 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 xem xét một giải pháp bằng cách sử dụng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102. Nó không hoạt động hoàn hảo, nhưng nó sử dụng các công cụ mà bạn đã biết, vì vậy đây là một nơi tốt để bắt đầu

Nhà sản xuất-Người tiêu dùng Sử dụng x = threading.Thread(target=thread_function, args=(1,), daemon=True) 102

Vì đây là một bài viết về Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6 và vì bạn vừa đọc về nguyên hàm của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102, hãy thử giải quyết vấn đề này với hai luồng bằng cách sử dụng một hoặc hai
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102.

Thiết kế chung là có một luồng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 đọc từ mạng giả mạo và đặt thông báo vào một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
10

Để tạo một tin nhắn giả mạo,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 nhận một số ngẫu nhiên từ một đến một trăm. Nó gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
166 trên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
167 để gửi nó đến
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 cũng sử dụng giá trị
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
170 để báo hiệu cho người tiêu dùng dừng lại sau khi đã gửi mười giá trị. Điều này hơi khó xử, nhưng đừng lo, bạn sẽ thấy các cách để loại bỏ giá trị
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
170 này sau khi bạn làm việc qua ví dụ này

Ở phía bên kia của

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
167 là người tiêu dùng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
11

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 đọc một tin nhắn từ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
167 và ghi nó vào một cơ sở dữ liệu giả mạo, trong trường hợp này chỉ là in nó ra màn hình. Nếu nó nhận được giá trị
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
170, nó sẽ trả về từ hàm, điều này sẽ kết thúc luồng

Trước khi bạn xem phần thực sự thú vị, phần

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157, đây là phần
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
94, tạo ra những chủ đề này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
12

Điều này trông khá quen thuộc vì nó gần với mã

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
94 trong các ví dụ trước

Hãy nhớ rằng bạn có thể bật ghi nhật ký

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
125 để xem tất cả các thông báo ghi nhật ký bằng cách bỏ ghi chú dòng này

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
13

Có thể đáng để xem qua các thông báo ghi nhật ký của

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
125 để xem chính xác vị trí mỗi chuỗi lấy và giải phóng các khóa

Bây giờ chúng ta hãy xem

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157 truyền tin nhắn từ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 đến
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
14

ồ. Đó là rất nhiều mã. Một tỷ lệ phần trăm khá cao trong số đó chỉ là ghi lại các câu lệnh để dễ dàng xem điều gì đang xảy ra khi bạn chạy nó. Đây là cùng một mã với tất cả các câu lệnh ghi nhật ký đã bị xóa

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
15

Điều đó có vẻ dễ quản lý hơn một chút.

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157 trong phiên bản mã này của bạn có ba thành viên

  1. x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    185 lưu trữ tin nhắn để vượt qua
  2. x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    186 là một đối tượng
    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    187 hạn chế quyền truy cập vào tin nhắn theo luồng
    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    163
  3. x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    189 cũng là một
    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    187 hạn chế quyền truy cập vào thư theo chuỗi
    x = threading.Thread(target=thread_function, args=(1,), daemon=True)
    
    168

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
192 khởi tạo ba thành viên này và sau đó gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
111 trên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
189. Đây là trạng thái bạn muốn bắt đầu.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 được phép thêm tin nhắn mới, nhưng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 cần đợi cho đến khi có tin nhắn

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
197 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
198 gần như đối lập nhau.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
197 cuộc gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
111 trên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
01. Đây là cuộc gọi sẽ khiến
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 đợi cho đến khi có tin nhắn

Khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 đã có được
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
189, nó sẽ sao chép giá trị trong
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
185 và sau đó gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112 trên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
186. Mở khóa này là điều cho phép
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 chèn thông báo tiếp theo vào
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
167

Trước khi bạn tiếp tục với

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
166, có một điều gì đó tế nhị đang diễn ra trong
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
197 mà bạn rất dễ bỏ sót. Nó có vẻ hấp dẫn để loại bỏ
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
12 và chỉ để hàm kết thúc bằng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
13. Xem liệ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 là câu trả lời. Ngay khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
15, nó có thể được hoán đổi và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 có thể bắt đầu chạy. Điều đó có thể xảy ra trước khi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112 trở lại. Điều này có nghĩa là có một khả năng nhỏ là khi hàm trả về
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
18, đó thực sự có thể là tin nhắn 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ề điều kiện chủng tộc

Chuyển sang

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
166, bạn có thể thấy mặt trái của giao dịch.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 sẽ gọi điều này bằng một tin nhắn. Nó sẽ lấy
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
186, đặt
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
185 và gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112 sau đó là
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
01, điều này sẽ cho phép
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 đọc giá trị đó

Hãy chạy mã có ghi nhật ký được đặt thành

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
133 và xem nó trông như thế nào

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
16

Lúc đầu, bạn có thể thấy lạ khi nhà sản xuất nhận được hai tin nhắn trước khi người tiêu dùng thậm chí chạy. Nếu bạn nhìn lại

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
166, bạn sẽ nhận thấy rằng nơi duy nhất mà nó chờ đợi một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 là khi nó cố gắng đưa thông báo vào đường dẫn. Việc này được thực hiện sau khi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 nhận được thông báo và ghi lại rằng nó đã nhận được thông báo đó

Khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 cố gắng gửi tin nhắn thứ hai này, nó sẽ gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
166 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 nó thường cho phép mỗi luồng có một khoảng thời gian hợp lý để chạy trước khi hoán đổi nó. Đó là lý do tại sao

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 thường chạy cho đến khi nó chặn lệnh gọi thứ hai tới
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
166

Tuy nhiên, khi một luồng bị chặn, hệ điều hành sẽ luôn hoán đổi nó và tìm một luồng khác để chạy. Trong trường hợp này, chuỗi duy nhất khác có bất kỳ việc gì phải làm là

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
197, đọc tin nhắn và gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112 trên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
186, do đó cho phép
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 chạy lại vào lần tiếp theo các chuỗi được hoán đổi

Lưu ý rằng tin nhắn đầu tiên là

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
41 và đó chính xác là những gì mà
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 đã đọc, mặc dù
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 đã tạo ra tin nhắn
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
44

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 sản xuất-người tiêu dùng nói chung vì nó chỉ cho phép một giá trị duy nhất trong quy trình tại một thời điểm. Khi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 nhận được một loạt tin nhắn, nó sẽ không có nơi nào để đặt chúng

Hãy chuyển sang một cách tốt hơn để giải quyết vấn đề này, sử dụng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46

Loại bỏ các quảng cáo

Nhà sản xuất-Người tiêu dùng Sử dụng x = threading.Thread(target=thread_function, args=(1,), daemon=True) 46

Nếu bạn muốn có thể xử lý nhiều giá trị trong quy trình cùng một lúc, bạn sẽ cần cấu trúc dữ liệu cho quy trình cho phép số lượng tăng và giảm khi dữ liệu được sao lưu từ

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163

Thư viện chuẩn của Python có một mô-đun

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
49, đến lượt nó, có một lớp
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46. Hãy thay đổi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157 để sử dụng một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46 thay vì chỉ một biến được bảo vệ bởi một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102. Bạn cũng sẽ sử dụng một cách khác để dừng các worker thread bằng cách sử dụng một nguyên hàm khác từ Python
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6, một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
55

Hãy bắt đầu với

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
55. Đối tượng
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
57 cho phép một luồng báo hiệu một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
58 trong khi nhiều luồng khác có thể chờ đợi điều đó xảy ra. Cách 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 việc chúng đang làm, chúng chỉ cần thỉnh thoảng kiểm tra trạng thái của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
55

Việc kích hoạt sự kiện có thể là nhiều thứ. Trong ví dụ này, luồng chính sẽ ngủ một lúc và sau đó

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
61 nó

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
17

Những thay đổi duy nhất ở đây là việc tạo đối tượng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
58 trên dòng 8, chuyển
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
58 làm tham số trên dòng 10 và 11, và phần cuối cùng trên dòng 13 đến 15, ngủ trong một giây, ghi nhật ký và sau đó gọi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 cũng không phải thay đổi quá nhiều

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
18

Bây giờ nó sẽ lặp lại cho đến khi thấy rằng sự kiện đã được đặt trên dòng 3. Nó cũng không còn đặt giá trị

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
170 vào
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
167

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 phải thay đổi thêm một chút

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
19

Trong khi bạn phải lấy mã liên quan đến giá trị

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
170, bạn phải thực hiện một điều kiện
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
70 phức tạp hơn một chút. Nó không chỉ lặp cho đến khi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
58 được đặt, mà còn cần tiếp tục lặp cho đến khi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
167 được làm trống

Đảm bảo hàng đợi trống trước khi người tiêu dùng kết thúc sẽ ngăn chặn một vấn đề thú vị khác. Nếu

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 thoát trong khi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
167 có tin nhắn trong đó, có hai điều tồi tệ 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à
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 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

Điều này xảy ra nếu

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
58 được kích hoạt sau khi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 đã kiểm tra điều kiện
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
78 nhưng trước khi nó gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
79

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 đầy đủ. Sau đó,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 sẽ gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
166 sẽ đợi cho đến khi có chỗ trống trên hàng đợi cho tin nhắn mới.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 đã thoát, vì vậy điều này sẽ không xảy ra và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 sẽ không thoát

Phần còn lại của

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 sẽ trông quen thuộc

Tuy nhiên,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157 đã thay đổi đáng kể

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
0

Bạn có thể thấy rằng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157 là một lớp con của
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
87.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46 có một tham số tùy chọn khi khởi tạo để chỉ định kích thước tối đa của hàng đợi

Nếu bạn cung cấp một số dương cho ________ 089, nó sẽ giới hạn hàng đợi ở số lượng phần tử đó, khiến cho

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
90 bị chặn cho đến khi có ít hơn ________ 089 phần tử. Nếu bạn không chỉ định
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
89, thì hàng đợi sẽ phát triển đến giới hạn bộ nhớ máy tính của bạn

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
197 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
166 nhỏ hơn nhiều. Về cơ bản, họ bọc
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
95 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
90 trên
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46. Bạn có thể tự hỏi tất cả các mã khóa ngăn chặn các chủ đề gây ra tình trạng cuộc đua đã đi đâu

Các nhà phát triển cốt lõi đã viết thư viện tiêu chuẩn biết rằng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46 thường được sử dụng trong môi trường đa luồng và đã kết hợp tất cả mã khóa đó bên trong chính
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46 là chủ đề an toàn

Chạy chương trình này trông giống như sau

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
1

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ị đang xảy ra. Ngay ở trên cùng, bạn có thể thấy

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 phải tạo năm tin nhắn và đặt bốn tin nhắn trong số đó vào hàng đợi. Nó đã bị hệ điều hành tráo đổi trước khi nó có thể đặt cái thứ năm

Sau đó,

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 đã chạy và gửi tin nhắn đầu tiên. Nó in ra thông báo đó cũng như độ sâu của hàng đợi tại thời điểm đó

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
2

Đây là cách bạn biết rằng tin nhắn thứ năm vẫn chưa được gửi đến ________ 2167. Hàng đợi giảm xuống kích thước ba sau khi một tin nhắn bị xóa. Bạn cũng biết rằng

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
49 có thể chứa mười tin nhắn, vì vậy chuỗi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 không bị chặn bởi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
49. Nó đã bị tráo đổi bởi hệ điều hành

Ghi chú. Đầu ra của bạn sẽ khác. Đầu ra của bạn sẽ thay đổi từ lần chạy này sang lần chạy khác. Đó là phần thú vị khi làm việc với các chủ đề

Khi chương trình bắt đầu kết thúc, bạn có thể thấy luồng chính tạo ra

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
58 khiến cho
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 thoát ngay lập tức không.
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 vẫn còn rất nhiều việc phải làm, vì vậy nó sẽ tiếp tục chạy cho đến khi dọn sạch
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
167

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 tới

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
73 trong
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
163 hoặc
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
168 để mô phỏng thời gian truy cập mạng hoặc ổ đĩa lâu 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 cũng 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 sản xuất-người tiêu dùng, nhưng bạn có thể đơn giản hóa nó hơn nữa.

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
157 thực sự không cần thiết cho vấn đề này. Khi bạn gỡ bỏ ghi nhật ký, nó sẽ trở thành một
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
87

Đây là mã cuối cùng trông như thế nào khi sử dụng trực tiếp

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
87

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
3

Điều đó dễ đọc hơn và cho thấy cách sử dụng các nguyên hàm tích hợp sẵn của Python có thể đơn giản hóa một vấn đề phức tạp

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
102 và
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
46 là các lớp tiện dụng để giải quyết các vấn đề tương tranh, nhưng có những lớp khác do thư viện chuẩn cung cấp. Trước khi bạn kết thúc hướng dẫn này, hãy thực hiện một cuộc khảo sát nhanh về một số trong số chúng

Loại bỏ các quảng cáo

đối tượng luồng

Có một vài nguyên mẫu khác được cung cấp bởi mô-đun Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6. Mặc dù bạn không cần những thứ này cho các ví dụ trên, nhưng chúng có thể hữu ích trong các trường hợp sử dụng khác nhau, vì vậy bạn nên làm quen với chúng

đèn hiệu

Đối tượng Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6 đầu tiên cần xem xét là
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
21. Một
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
22 là một bộ đếm có một vài thuộc tính đặc biệt. Đầu tiên là việc đếm là nguyên tử. Điều này có nghĩa là đảm bảo rằng hệ điều hành sẽ không tráo đổi luồng khi đang tăng hoặc giảm bộ đếm

Bộ đếm nội bộ được tăng lên khi bạn gọi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112 và giảm đi khi bạn gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
111

Thuộc tính đặc biệt tiếp theo là nếu một luồng gọi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
111 khi bộ đếm bằng 0, luồng đó sẽ chặn cho đến khi một luồng khác gọi
x = threading.Thread(target=thread_function, args=(1,), daemon=True)
112 và tăng bộ đếm lên một

Semaphores thường được sử dụng để bảo vệ tài nguyên có dung lượng 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ờ

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
27 là một cách để lên lịch 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 thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
28 bằng cách chuyển trong một số giây để đợi và một chức năng để gọi

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
4

Bạn bắt đầu

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
28 bằng cách gọi
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
5. Hàm sẽ được gọi trên một luồng mới vào một thời điểm nào đó sau thời gian đã chỉ định, nhưng hãy lưu ý rằng không có gì hứa hẹn rằng nó sẽ được gọi chính xác vào thời điểm bạn muốn

Nếu bạn muốn dừng một

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
28 mà bạn đã bắt đầu, bạn có thể hủy nó bằng cách gọi cho
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
32. Gọi
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
32 sau khi
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
28 đã kích hoạt không làm gì cả và không tạo ra ngoại lệ

Một

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
28 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 thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
28 hết hạn, thì có thể gọi
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
32

Rào chắn

Một

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
38 có thể được sử dụng để đồng bộ hóa một số luồng cố định. Khi tạo một
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
39, người gọi phải chỉ định có bao nhiêu luồng sẽ được đồng bộ hóa trên đó. Mỗi luồng gọi
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
40 trên
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
39. Tất cả chúng sẽ vẫn bị chặn cho đến khi số lượng chủ đề được chỉ định đang chờ và sau đó tất cả sẽ được giải phóng cùng một lúc

Hãy nhớ rằng các luồng được lên lịch bởi hệ điều hành, vì vậy, mặc dù tất cả các luồng được giải phóng đồng thời, chúng sẽ được lên lịch để chạy từng luồng một

Một cách sử dụng cho

$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
39 là cho phép một nhóm các luồng tự khởi tạo. Để các luồng chờ trên
$ ./daemon_thread.py
Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
39 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 kết thúc quá trình khởi tạo của chúng

Sự kết luận. Luồng trong Python

Bây giờ bạn đã thấy phần lớn những gì Python

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
6 cung cấp và một số ví dụ về cách xây dựng các chương trình luồng và các vấn đề mà chúng giải quyết. Bạn cũng đã thấy một vài ví dụ về các vấn đề phát sinh khi viết và gỡ lỗi các chương trình luồng.

Nếu bạn muốn khám phá các tùy chọn khác để đồng thời trong Python, hãy xem Tăng tốc chương trình Python của bạn với đồng thời

Nếu bạn muốn tìm hiểu sâu về mô-đun

x = threading.Thread(target=thread_function, args=(1,), daemon=True)
8, hãy đọc Async IO trong Python. Hướng dẫn hoàn chỉnh

Dù bạn làm gì, giờ đây bạn đã có thông tin và sự tự tin cần thiết để viết chương trình bằng luồng Python

Đặc biệt cảm ơn độc giả JL Diaz đã giúp làm sạch phần giới thiệu

Lấy bài kiểm tra. Kiểm tra kiến ​​thức của bạn với bài kiểm tra tương tác “Python Threading” của chúng tôi. Sau khi hoàn thành, bạn sẽ nhận được điểm số để có thể theo dõi quá trình học tập của mình theo thời gian

Lấy bài kiểm tra "

Đánh dấu là đã hoàn thành

Xem ngay Hướng dẫn này có một khóa học video liên quan do nhóm Real Python tạo. Xem nó cùng với hướng dẫn bằng văn bản để hiểu sâu hơn. Tạo luồng trong Python

🐍 Thủ thuật Python 💌

Nhận một Thủ thuật Python ngắn và hấp dẫn được gửi đến hộp thư đến của bạn vài ngày một lần. Không có thư rác bao giờ. Hủy đăng ký bất cứ lúc nào. Được quản lý bởi nhóm Real Python

Giá trị trả về của bộ đếm thời gian luồng python

Gửi cho tôi thủ thuật Python »

Về Jim Anderson

Giá trị trả về của bộ đếm thời gian luồng python
Giá trị trả về của bộ đếm thời gian luồng python

Jim đã lập trình trong một thời gian dài bằng nhiều ngôn ngữ. Anh ấy đã làm việc trên các hệ thống nhúng, xây dựng các hệ thống xây dựng phân tán, quản lý nhà cung cấp nước ngoài và tham gia rất nhiều cuộc họp

» Thông tin thêm về Jim


Mỗi hướng dẫn tại Real Python được tạo bởi một nhóm các nhà phát triển để nó đáp ứng các tiêu chuẩn chất lượng cao của chúng tôi. Các thành viên trong nhóm đã làm việc trong hướng dẫn này là

Giá trị trả về của bộ đếm thời gian luồng python

Aldren

Giá trị trả về của bộ đếm thời gian luồng python

Brad

Giá trị trả về của bộ đếm thời gian luồng python

Joanna

Bậc thầy Kỹ năng Python trong thế giới thực Với quyền truy cập không giới hạn vào Python thực

Giá trị trả về của bộ đếm thời gian luồng python

Tham gia với chúng tôi và có quyền truy cập vào hàng nghìn hướng dẫn, khóa học video thực hành và cộng đồng các Pythonistas chuyên gia

Nâng cao kỹ năng Python của bạn »

Bậc thầy Kỹ năng Python trong thế giới thực
Với quyền truy cập không giới hạn vào Python thực

Tham gia với chúng tôi và có quyền truy cập vào hàng ngàn hướng dẫn, khóa học video thực hành và cộng đồng Pythonistas chuyên gia

Nâng cao kỹ năng Python của bạn »

Bạn nghĩ sao?

Đánh giá bài viết này

Tweet Chia sẻ Chia sẻ Email

Bài học số 1 hoặc điều yêu thích mà bạn đã học được là gì?

Mẹo bình luận. Những nhận xét hữu ích nhất là những nhận xét được viết với mục đích học hỏi hoặc giúp đỡ các sinh viên khác. Nhận các mẹo để đặt câu hỏi hay và nhận câu trả lời cho các câu hỏi phổ biến trong cổng thông tin hỗ trợ của chúng tôi

Chuỗi Python có thể trả về giá trị không?

Một chuỗi không thể trả về giá trị trực tiếp . Phương thức start() trên một luồng gọi phương thức run() của luồng thực thi mã của chúng ta trong một luồng thực thi mới. Đến lượt phương thức run() có thể gọi một hàm đích, nếu được định cấu hình.

Làm thế nào để bộ đếm thời gian luồng hoạt động Python?

Phân luồng trong Python Timer() bắt đầu sau độ trễ được xác định là đối số . Do đó, lớp Timer gọi chính nó trì hoãn việc thực hiện thao tác sau trong cùng một khoảng thời gian được chỉ định.

Hạn chế chính của luồng với Python là gì?

Do cách triển khai CPython của Python hoạt động, luồng có thể không tăng tốc tất cả các tác 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 chuỗi Python chạy tại một thời điểm. Các tác vụ dành nhiều thời gian chờ đợi các sự kiện bên ngoài thường là những ứng cử viên tốt cho luồng.