Python không đồng bộ khi nào?

Là một nhà phát triển cốt lõi của Python đã khiến tôi muốn hiểu cách thức hoạt động của ngôn ngữ này. Tôi nhận ra rằng sẽ luôn có những góc khuất mà tôi không biết mọi chi tiết phức tạp, nhưng để có thể trợ giúp về các vấn đề và thiết kế chung của Python, tôi cảm thấy mình nên thử và hiểu ngữ nghĩa cốt lõi của nó cũng như cách mọi thứ hoạt động bên trong.

Nhưng cho đến gần đây tôi không hiểu cách thức hoạt động của

for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 trong Python 3. 5. Tôi biết rằng
for x in iterator:
    yield x
5 trong Python 3. 3 kết hợp với
for x in iterator:
    yield x
6 trong Python 3. 4 đã dẫn đến cú pháp mới này. Nhưng việc không thực hiện nhiều công việc liên quan đến mạng -- thứ mà
for x in iterator:
    yield x
6 không giới hạn mà còn tập trung vào -- đã khiến tôi không thực sự chú ý nhiều đến tất cả những thứ
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 này. ý tôi là tôi biết điều đó

yield from iterator

là [về cơ bản] tương đương với

for x in iterator:
    yield x

Và tôi biết rằng

for x in iterator:
    yield x
6 là một khung vòng lặp sự kiện cho phép lập trình không đồng bộ và tôi biết những từ đó [về cơ bản] có nghĩa là gì. Nhưng chưa bao giờ đi sâu vào cú pháp
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 để hiểu tất cả những thứ này kết hợp với nhau như thế nào, tôi cảm thấy mình không hiểu lập trình bất đồng bộ trong Python, điều này làm tôi khó chịu. Vì vậy, tôi quyết định dành thời gian và thử tìm hiểu xem tất cả những thứ đó hoạt động như thế nào. Và vì tôi đã nghe từ nhiều người rằng họ cũng không hiểu cách thức hoạt động của thế giới lập trình không đồng bộ mới này, nên tôi quyết định viết bài luận này [vâng, bài đăng này đã mất rất nhiều thời gian và dài đến nỗi vợ tôi

Bây giờ vì tôi muốn hiểu đúng về cách thức hoạt động của cú pháp, bài tiểu luận này có một số chi tiết kỹ thuật cấp thấp về cách CPython thực hiện mọi việc. Hoàn toàn ổn nếu nó chi tiết hơn bạn muốn hoặc bạn không hiểu hết về nó vì tôi không giải thích mọi sắc thái của nội bộ CPython để giữ cho điều này không biến thành một cuốn sách [e. g. , nếu bạn không biết rằng các đối tượng mã có cờ, mặc kệ đối tượng mã là gì, cũng không sao và bạn không cần quan tâm để lấy thứ gì từ tiểu luận này]. Tôi đã cố gắng cung cấp một bản tóm tắt dễ tiếp cận hơn ở cuối mỗi phần để bạn có thể lướt qua các chi tiết nếu chúng trở nên nhiều hơn những gì bạn muốn giải quyết.

Một bài học lịch sử về coroutines trong Python

Theo Wikipedia, "Coroutines là các thành phần chương trình máy tính tổng quát hóa các chương trình con cho đa nhiệm không ưu tiên, bằng cách cho phép nhiều điểm vào để tạm dừng và tiếp tục thực thi tại các vị trí nhất định". Đó là một cách nói khá kỹ thuật, "các coroutines là các chức năng mà bạn có thể tạm dừng thực thi". Và nếu bạn đang nói với chính mình, "nghe giống như máy phát điện", bạn sẽ đúng

Quay lại Python 2. 2, các trình tạo được giới thiệu lần đầu tiên bởi PEP 255 [chúng còn được gọi là trình lặp trình tạo vì trình tạo triển khai giao thức trình lặp]. Lấy cảm hứng chủ yếu từ ngôn ngữ lập trình Biểu tượng, các trình tạo cho phép tạo một trình vòng lặp không lãng phí bộ nhớ khi tính toán giá trị tiếp theo trong phép lặp theo cách đơn giản [bạn luôn có thể tạo một lớp triển khai

for x in iterator:
    yield x
23 và
for x in iterator:
    yield x
24 mà . Ví dụ: nếu bạn muốn tạo phiên bản
for x in iterator:
    yield x
25 của riêng mình, bạn có thể thực hiện điều đó một cách háo hức bằng cách tạo một danh sách các số nguyên

for x in iterator:
    yield x
2

Tuy nhiên, vấn đề với điều này là nếu bạn muốn một dãy lớn như các số nguyên từ 0 đến 1.000.000, bạn phải tạo một danh sách đủ dài để chứa 1.000.000 số nguyên. Nhưng khi các trình tạo được thêm vào ngôn ngữ, bạn có thể đột nhiên tạo một trình vòng lặp mà không cần phải tạo toàn bộ chuỗi mà không tốn nhiều công sức. Thay vào đó, tất cả những gì bạn phải làm là có đủ bộ nhớ cho một số nguyên tại một thời điểm

for x in iterator:
    yield x
6

Có một chức năng tạm dừng những gì nó đang làm bất cứ khi nào nó chạm vào biểu thức

for x in iterator:
    yield x
26 - mặc dù đó là một câu lệnh cho đến Python 2. 5 -- và sau đó có thể tiếp tục sau đó rất hữu ích về mặt sử dụng ít bộ nhớ hơn, cho phép ý tưởng về các chuỗi vô hạn, v.v.

Nhưng như bạn có thể nhận thấy, tất cả các trình tạo đều liên quan đến trình vòng lặp. Bây giờ có một cách tốt hơn để tạo các trình vòng lặp rõ ràng là tuyệt vời [và điều này được thể hiện khi bạn xác định một phương thức

for x in iterator:
    yield x
23 trên một đối tượng làm trình tạo], nhưng mọi người biết rằng nếu chúng ta lấy phần "tạm dừng" của trình tạo và thêm vào phần "gửi . Và tính năng chính xác của việc gửi nội dung vào trình tạo bị tạm dừng đã được thêm vào Python 2. 5 cảm ơn PEP 342. Trong số những thứ khác, PEP 342 đã giới thiệu phương pháp
for x in iterator:
    yield x
28 trên máy phát điện. Điều này cho phép một người không chỉ tạm dừng trình tạo mà còn gửi giá trị trở lại trình tạo nơi nó tạm dừng. Tiếp tục lấy ví dụ về
for x in iterator:
    yield x
25 của chúng ta, bạn có thể làm cho chuỗi nhảy tiến hoặc lùi một lượng nào đó

for x in iterator:
    yield x
1

Máy phát điện không bị lẫn lộn nữa cho đến Python 3. 3 khi PEP 380 được thêm vào

for x in iterator:
    yield x
5. Nói một cách chính xác, tính năng này trao quyền cho bạn tái cấu trúc các trình tạo một cách rõ ràng bằng cách giúp dễ dàng tạo ra mọi giá trị từ một trình vòng lặp [điều mà một trình tạo thuận tiện xảy ra]

for x in iterator:
    yield x
3

Nhờ làm cho việc tái cấu trúc trở nên dễ dàng hơn,

for x in iterator:
    yield x
5 cũng cho phép bạn xâu chuỗi các trình tạo lại với nhau để các giá trị nổi lên và xuống trong ngăn xếp cuộc gọi mà không cần mã phải làm bất kỳ điều gì đặc biệt

for x in iterator:
    yield x
5

Bản tóm tắt

Trình tạo trong Python 2. 2 tạm dừng thực thi mã. Sau khi khả năng gửi giá trị trở lại trình tạo bị tạm dừng đã được giới thiệu trong Python 2. 5, khái niệm về coroutines trong Python đã trở nên khả thi. Và việc bổ sung

for x in iterator:
    yield x
5 trong Python 3. 3 giúp tái cấu trúc các trình tạo cũng như xâu chuỗi chúng lại với nhau dễ dàng hơn

Vòng lặp sự kiện là gì?

Điều quan trọng là phải hiểu vòng lặp sự kiện là gì và cách chúng có thể lập trình không đồng bộ nếu bạn quan tâm đến

for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4. Nếu bạn đã từng lập trình GUI trước đây -- bao gồm cả công việc giao diện người dùng web -- thì bạn đã từng làm việc với một vòng lặp sự kiện. Nhưng vì khái niệm lập trình không đồng bộ như một cấu trúc ngôn ngữ là mới trong Python, sẽ không sao nếu bạn không biết vòng lặp sự kiện là gì

Quay trở lại Wikipedia, vòng lặp sự kiện "là một cấu trúc lập trình chờ đợi và gửi các sự kiện hoặc thông báo trong một chương trình". Về cơ bản, một vòng lặp sự kiện cho phép bạn thực hiện, "khi A xảy ra, hãy làm B". Có lẽ ví dụ đơn giản nhất để giải thích điều này là vòng lặp sự kiện JavaScript có trong mọi trình duyệt. Bất cứ khi nào bạn nhấp vào thứ gì đó ["khi A xảy ra"], lần nhấp đó sẽ được đưa vào vòng lặp sự kiện JavaScript để kiểm tra xem có bất kỳ cuộc gọi lại

for x in iterator:
    yield x
65 nào được đăng ký để xử lý lần nhấp đó ["do B"]. Nếu bất kỳ cuộc gọi lại nào đã được đăng ký thì cuộc gọi lại được gọi với các chi tiết về lần nhấp. Vòng lặp sự kiện được coi là một vòng lặp vì nó liên tục thu thập các sự kiện và lặp lại chúng để tìm xem phải làm gì với sự kiện

Trong trường hợp của Python,

for x in iterator:
    yield x
6 đã được thêm vào thư viện chuẩn để cung cấp vòng lặp sự kiện. Có một trọng tâm về kết nối mạng trong
for x in iterator:
    yield x
6, trong trường hợp của vòng lặp sự kiện là biến "khi A xảy ra" thành khi I/O từ ổ cắm sẵn sàng để đọc và/hoặc ghi [thông qua mô-đun
for x in iterator:
    yield x
68]. Khác với GUI và I/O, các vòng lặp sự kiện cũng thường được sử dụng để thực thi mã trong một luồng hoặc quy trình con khác và để vòng lặp sự kiện đóng vai trò là bộ lập lịch [i. e. , đa nhiệm hợp tác]. Nếu bạn tình cờ hiểu GIL của Python, các vòng lặp sự kiện sẽ hữu ích trong trường hợp việc phát hành GIL là có thể và hữu ích

Bản tóm tắt

Các vòng lặp sự kiện cung cấp một vòng lặp cho phép bạn nói, "khi A xảy ra thì hãy làm B". Về cơ bản, một vòng lặp sự kiện sẽ đề phòng khi có điều gì đó xảy ra và khi điều gì đó mà vòng lặp sự kiện quan tâm xảy ra thì nó sẽ gọi bất kỳ mã nào quan tâm đến điều gì đã xảy ra. Python đã đạt được một vòng lặp sự kiện trong thư viện chuẩn ở dạng

for x in iterator:
    yield x
6 trong Python 3. 4

Cách thức hoạt động của
for x in iterator:
    yield x
3 và
for x in iterator:
    yield x
4

Nó giống như trong Python 3. 4

Giữa các trình tạo được tìm thấy trong Python 3. 3 và một vòng lặp sự kiện ở dạng

for x in iterator:
    yield x
6, Python 3. 4 đã đủ để hỗ trợ lập trình không đồng bộ ở dạng lập trình đồng thời. Lập trình không đồng bộ về cơ bản là lập trình mà thứ tự thực hiện không được biết trước [do đó không đồng bộ thay vì đồng bộ]. Lập trình đồng thời là viết mã để thực thi độc lập với các phần khác, ngay cả khi tất cả thực thi trong một luồng [đồng thời không phải là song song]. Ví dụ, sau đây là Python 3. 4 để đếm ngược mỗi giây trong hai lệnh gọi hàm đồng thời, không đồng bộ

for x in iterator:
    yield x
7

Trong Trăn 3. 4, trình trang trí

for x in iterator:
    yield x
13 được sử dụng để gắn nhãn cho một chức năng hoạt động như một quy trình đăng quang được sử dụng với
for x in iterator:
    yield x
6 và vòng lặp sự kiện của nó. Điều này đã mang lại cho Python định nghĩa cụ thể đầu tiên về coroutine. một đối tượng đã triển khai các phương thức được thêm vào trình tạo trong PEP 342 và được đại diện bởi lớp cơ sở trừu tượng
for x in iterator:
    yield x
15. Điều này có nghĩa là đột nhiên tất cả các trình tạo triển khai giao diện coroutine ngay cả khi chúng không được sử dụng theo kiểu đó. Để khắc phục điều này,
for x in iterator:
    yield x
6 yêu cầu tất cả các máy phát điện được sử dụng như một coroutine phải được trang trí bằng
for x in iterator:
    yield x
13

Với định nghĩa cụ thể về quy trình đăng ký này [phù hợp với API mà trình tạo đã cung cấp], sau đó bạn đã sử dụng

for x in iterator:
    yield x
5 trên bất kỳ đối tượng
for x in iterator:
    yield x
19 nào để chuyển nó xuống vòng lặp sự kiện, tạm dừng thực thi quy trình đăng ký trong khi bạn chờ đợi điều gì đó xảy ra [là một tương lai . Khi đối tượng tương lai đạt đến vòng lặp sự kiện, nó được theo dõi ở đó cho đến khi đối tượng tương lai hoàn thành bất cứ điều gì nó cần làm. Khi tương lai đã hoàn thành công việc của nó, vòng lặp sự kiện nhận thấy và coroutine bị tạm dừng chờ kết quả của tương lai bắt đầu lại với kết quả của nó được gửi trở lại coroutine bằng phương thức
for x in iterator:
    yield x
28 của nó

Lấy ví dụ của chúng tôi ở trên. Vòng lặp sự kiện bắt đầu từng lệnh gọi quy trình đăng ký

for x in iterator:
    yield x
32, thực thi cho đến khi nó chạm vào
for x in iterator:
    yield x
5 và hàm
for x in iterator:
    yield x
34 ở một trong số chúng. Điều đó trả về một đối tượng
for x in iterator:
    yield x
19 được truyền xuống vòng lặp sự kiện và tạm dừng thực thi coroutine. Ở đó, vòng lặp sự kiện theo dõi đối tượng trong tương lai cho đến khi một giây kết thúc [cũng như kiểm tra những thứ khác mà nó đang xem, chẳng hạn như coroutine khác]. Sau khi hết một giây, vòng lặp sự kiện lấy quy trình đăng ký
for x in iterator:
    yield x
32 bị tạm dừng đã cung cấp cho vòng lặp sự kiện đối tượng tương lai, gửi kết quả của đối tượng tương lai trở lại quy trình đăng ký đã cung cấp cho nó đối tượng tương lai ngay từ đầu và quy trình đăng ký bắt đầu . Điều này tiếp tục cho đến khi tất cả các quy trình đăng ký
for x in iterator:
    yield x
32 chạy xong và vòng lặp sự kiện không còn gì để xem. Tôi sẽ thực sự cho bạn thấy một ví dụ hoàn chỉnh về cách hoạt động chính xác của tất cả các công cụ trong vòng lặp sự kiện/quy trình đăng ký này sau, nhưng trước tiên tôi muốn giải thích cách thức hoạt động của
for x in iterator:
    yield x
3 và
for x in iterator:
    yield x
4

Đi từ
for x in iterator:
    yield x
5 đến
for x in iterator:
    yield x
4 trong Python 3. 5

Trong Trăn 3. 4, một hàm được gắn cờ là một coroutine cho mục đích lập trình không đồng bộ trông giống như

for x in iterator:
    yield x
7

Trong Trăn 3. 5, trình trang trí

for x in iterator:
    yield x
52 đã được thêm vào để gắn cờ trình tạo là một coroutine giống như
for x in iterator:
    yield x
13. Bạn cũng có thể sử dụng
for x in iterator:
    yield x
54 để xác định về mặt cú pháp một hàm là một coroutine, mặc dù nó không thể chứa bất kỳ dạng biểu thức
for x in iterator:
    yield x
26 nào;

for x in iterator:
    yield x
4

Tuy nhiên, một điều quan trọng mà

for x in iterator:
    yield x
3 và
for x in iterator:
    yield x
52 làm là thắt chặt định nghĩa về coroutine là gì. Nó đưa các coroutine từ chỗ chỉ đơn giản là một giao diện thành một loại thực tế, làm cho sự khác biệt giữa bất kỳ trình tạo nào và trình tạo có nghĩa là một coroutine nghiêm ngặt hơn nhiều [và chức năng
for x in iterator:
    yield x
70 thậm chí còn chặt chẽ hơn bằng cách nói rằng phải sử dụng
for x in iterator:
    yield x
3]

Bạn cũng sẽ nhận thấy rằng ngoài

for x in iterator:
    yield x
3, Python 3. 5 ví dụ giới thiệu biểu thức
for x in iterator:
    yield x
4 [chỉ hợp lệ trong một
for x in iterator:
    yield x
54]. Trong khi
for x in iterator:
    yield x
4 hoạt động giống như
for x in iterator:
    yield x
5, các đối tượng được chấp nhận bởi một biểu thức
for x in iterator:
    yield x
4 là khác nhau. Các coroutines chắc chắn được cho phép trong một biểu thức
for x in iterator:
    yield x
4 vì khái niệm về các coroutines là nền tảng trong tất cả những điều này. Nhưng khi bạn gọi
for x in iterator:
    yield x
4 trên một đối tượng, về mặt kỹ thuật, nó cần phải là một đối tượng có thể chờ đợi. một đối tượng định nghĩa một phương thức
for x in iterator:
    yield x
70 trả về một trình vòng lặp không phải là một coroutine. Bản thân các coroutine cũng được coi là các đối tượng có thể chờ đợi [do đó tại sao
for x in iterator:
    yield x
15 kế thừa từ
for x in iterator:
    yield x
72]. Định nghĩa này tuân theo truyền thống của Python về việc biến hầu hết các cấu trúc cú pháp thành một lệnh gọi phương thức bên dưới mui xe, giống như
for x in iterator:
    yield x
73 là
for x in iterator:
    yield x
74 hoặc
for x in iterator:
    yield x
75 bên dưới tất cả

Làm thế nào để sự khác biệt giữa

for x in iterator:
    yield x
5 và
for x in iterator:
    yield x
4 diễn ra ở mức độ thấp [i. e. , một máy phát điện với
for x in iterator:
    yield x
52 so với. một với
for x in iterator:
    yield x
54]? . 5 để có được các chi tiết thực chất. Mã byte cho
for x in iterator:
    yield x
40 là

for x in iterator:
    yield x
0

Mã byte cho

for x in iterator:
    yield x
41 là

for x in iterator:
    yield x
1

Bỏ qua sự khác biệt về số dòng do

for x in iterator:
    yield x
40 có trình trang trí
for x in iterator:
    yield x
13, sự khác biệt duy nhất có thể nhìn thấy giữa chúng là opcode
for x in iterator:
    yield x
44 so với opcode
for x in iterator:
    yield x
45. Cả hai chức năng đều được gắn cờ chính xác là coroutines, vì vậy không có sự khác biệt nào ở đó. Trong trường hợp của
for x in iterator:
    yield x
44, nó chỉ cần kiểm tra xem đối số của nó là một trình tạo hay coroutine, nếu không, nó gọi
for x in iterator:
    yield x
47 trên đối số của nó [việc chấp nhận một đối tượng coroutine bởi opcode cho
for x in iterator:
    yield x
5 chỉ được phép khi opcode được sử dụng từ bên trong chính coroutine

Nhưng

for x in iterator:
    yield x
45 làm điều gì đó khác biệt. Mặc dù mã byte sẽ chấp nhận một coroutine giống như
for x in iterator:
    yield x
44, nhưng nó sẽ không chấp nhận một trình tạo nếu chưa được gắn cờ là một coroutine. Tuy nhiên, ngoài các coroutine, mã byte sẽ chấp nhận một đối tượng có thể chờ đợi như đã thảo luận trước đó. Điều này làm cho các biểu thức
for x in iterator:
    yield x
5 và biểu thức
for x in iterator:
    yield x
4 đều chấp nhận các coroutine trong khi khác nhau về việc chúng có chấp nhận các trình tạo đơn giản hoặc các đối tượng có thể chờ đợi tương ứng hay không

Bạn có thể thắc mắc tại sao sự khác biệt giữa những gì một coroutine dựa trên

for x in iterator:
    yield x
3 và một coroutine dựa trên trình tạo sẽ chấp nhận trong các biểu thức tạm dừng tương ứng của chúng? . Vì các trình tạo vốn đã triển khai API cho các coroutine nên sẽ rất dễ vô tình sử dụng một trình tạo trong khi bạn thực sự dự kiến ​​sẽ sử dụng một coroutine. Và vì không phải tất cả các trình tạo đều được viết để sử dụng trong luồng điều khiển dựa trên coroutine, nên bạn cần tránh vô tình sử dụng trình tạo không chính xác. Nhưng vì Python không được biên dịch tĩnh, ngôn ngữ tốt nhất có thể cung cấp là kiểm tra thời gian chạy khi sử dụng quy trình đăng ký do trình tạo xác định. Điều này có nghĩa là khi sử dụng
for x in iterator:
    yield x
52, trình biên dịch của Python không thể biết liệu một trình tạo sẽ được sử dụng như một quy trình đăng quang hay chỉ là một trình tạo đơn giản [hãy nhớ rằng chỉ vì cú pháp nói
for x in iterator:
    yield x
52 không có nghĩa là ai đó chưa hoàn thành trước đó

Một điểm rất quan trọng mà tôi muốn làm rõ về sự khác biệt giữa một coroutine dựa trên trình tạo và một

for x in iterator:
    yield x
3 là chỉ những coroutine dựa trên trình tạo mới thực sự có thể tạm dừng thực thi và buộc một thứ gì đó được gửi xuống vòng lặp sự kiện. Bạn thường không thấy chi tiết rất quan trọng này vì bạn thường gọi các hàm dành riêng cho vòng lặp sự kiện như hàm
for x in iterator:
    yield x
34 vì các vòng lặp sự kiện triển khai API của riêng chúng và đây là loại hàm phải lo lắng về chi tiết nhỏ này. Đối với đại đa số chúng ta, chúng ta sẽ làm việc với các vòng lặp sự kiện hơn là viết chúng và do đó chỉ viết
for x in iterator:
    yield x
3 coroutines và không bao giờ cần thực sự quan tâm đến điều này. Nhưng nếu bạn giống tôi và đang thắc mắc tại sao bạn không thể viết một cái gì đó như
for x in iterator:
    yield x
34 chỉ bằng cách sử dụng
for x in iterator:
    yield x
3 coroutines, thì đây có thể là "aha. " khoảng khăc

Bản tóm tắt

Hãy tóm tắt tất cả những điều này thành các thuật ngữ đơn giản hơn. Xác định một phương thức với

for x in iterator:
    yield x
54 làm cho nó trở thành một coroutine. Một cách khác để tạo một coroutine là gắn cờ một trình tạo bằng
for x in iterator:
    yield x
52 -- về mặt kỹ thuật, cờ là cờ
for x in iterator:
    yield x
00 trên một đối tượng mã -- hoặc một lớp con của
for x in iterator:
    yield x
15. Bạn chỉ có thể tạm dừng chuỗi cuộc gọi coroutine với một coroutine dựa trên trình tạo

Một đối tượng có thể chờ đợi là một coroutine hoặc một đối tượng xác định

for x in iterator:
    yield x
70 -- về mặt kỹ thuật là
for x in iterator:
    yield x
72 -- trả về một iterator không phải là một coroutine. Biểu thức
for x in iterator:
    yield x
4 về cơ bản là
for x in iterator:
    yield x
5 nhưng với các hạn chế chỉ hoạt động với các đối tượng có thể chờ đợi [trình tạo đơn giản sẽ không hoạt động với biểu thức
for x in iterator:
    yield x
4]. Hàm
for x in iterator:
    yield x
3 là một coroutine có các câu lệnh
for x in iterator:
    yield x
56 -- bao gồm cả _______025 ẩn ở cuối mỗi hàm trong Python -- và/hoặc các biểu thức
for x in iterator:
    yield x
4 [không được phép sử dụng các biểu thức
for x in iterator:
    yield x
26]. Các hạn chế đối với các hàm
for x in iterator:
    yield x
3 là để đảm bảo rằng bạn không vô tình trộn và khớp các coroutine dựa trên trình tạo với các trình tạo khác vì mục đích sử dụng dự kiến ​​của hai loại trình tạo này khá khác nhau

Hãy nghĩ về
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 như một API cho lập trình không đồng bộ

Một điều quan trọng mà tôi muốn chỉ ra thực sự là điều mà tôi đã không thực sự nghĩ sâu về nó cho đến khi tôi xem bài phát biểu quan trọng về Python Brasil 2015 của David Beazley. Trong buổi nói chuyện đó, David đã chỉ ra rằng

for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 thực sự là một API dành cho lập trình không đồng bộ [anh ấy đã nhắc lại với tôi trên Twitter]. Ý của David khi nói điều này là mọi người không nên nghĩ rằng
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 đồng nghĩa với
for x in iterator:
    yield x
6, mà thay vào đó hãy nghĩ rằng
for x in iterator:
    yield x
6 là một khuôn khổ có thể sử dụng API
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 cho lập trình không đồng bộ

David thực sự tin rằng ý tưởng về việc

for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 là một API lập trình không đồng bộ mà anh ấy đã tạo dự án
for x in iterator:
    yield x
41 để triển khai vòng lặp sự kiện của riêng mình. Điều này đã giúp tôi hiểu rõ rằng
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 cho phép Python cung cấp các khối xây dựng cho lập trình không đồng bộ, nhưng không ràng buộc bạn vào một vòng lặp sự kiện cụ thể hoặc các chi tiết cấp thấp khác [không giống như các ngôn ngữ lập trình khác tích hợp vòng lặp sự kiện vào . Điều này cho phép các dự án như
for x in iterator:
    yield x
41 không chỉ hoạt động khác ở cấp độ thấp hơn [e. g. ,
for x in iterator:
    yield x
6 sử dụng các đối tượng trong tương lai làm API để giao tiếp với vòng lặp sự kiện của nó trong khi
for x in iterator:
    yield x
41 sử dụng các bộ dữ liệu], nhưng cũng có các đặc điểm hiệu suất và tiêu điểm khác nhau [e. g. ,
for x in iterator:
    yield x
6 có toàn bộ khuôn khổ để triển khai các lớp vận chuyển và giao thức giúp nó có thể mở rộng trong khi
for x in iterator:
    yield x
41 đơn giản hơn và mong người dùng lo lắng về loại điều đó nhưng cũng cho phép nó chạy nhanh hơn]

Dựa trên lịch sử [ngắn] của lập trình không đồng bộ trong Python, có thể hiểu rằng mọi người có thể nghĩ rằng

for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 ==
for x in iterator:
    yield x
6. Ý tôi là
for x in iterator:
    yield x
6 là thứ đã giúp lập trình không đồng bộ trong Python 3. 4 và là một yếu tố thúc đẩy để thêm
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 vào Python 3. 5. Nhưng thiết kế của
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 đủ linh hoạt để không yêu cầu
for x in iterator:
    yield x
6 hoặc bóp méo bất kỳ quyết định thiết kế quan trọng nào chỉ dành cho khuôn khổ đó. Nói cách khác,
for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 tiếp tục truyền thống của Python là thiết kế mọi thứ trở nên linh hoạt nhất có thể trong khi vẫn thực dụng để sử dụng [và triển khai]

Một ví dụ

Tại thời điểm này, đầu của bạn có thể tràn ngập các thuật ngữ và khái niệm mới, khiến bạn hơi khó nắm bắt đầy đủ cách thức hoạt động của tất cả những điều này để cung cấp cho bạn lập trình không đồng bộ. Để giúp làm cho mọi thứ trở nên cụ thể hơn, đây là một ví dụ lập trình không đồng bộ hoàn chỉnh [nếu giả định], từ đầu đến cuối từ vòng lặp sự kiện và các chức năng được liên kết với mã người dùng. Ví dụ này có các coroutines đại diện cho các lần phóng tên lửa riêng lẻ nhưng dường như đang đếm ngược đồng thời. Đây là lập trình không đồng bộ thông qua đồng thời;

for x in iterator:
    yield x
2

Như tôi đã nói, nó đã được tạo ra, nhưng nếu bạn chạy cái này trong Python 3. 5, bạn sẽ nhận thấy rằng cả ba coroutine đều chạy độc lập trong một luồng và tổng thời gian chạy là khoảng 5 giây. Bạn có thể coi

for x in iterator:
    yield x
60,
for x in iterator:
    yield x
61 và
for x in iterator:
    yield x
62 là những gì mà nhà cung cấp vòng lặp sự kiện như
for x in iterator:
    yield x
6 và
for x in iterator:
    yield x
41 sẽ cung cấp cho bạn. Đối với người dùng bình thường, chỉ có mã trong
for x in iterator:
    yield x
32 và
for x in iterator:
    yield x
66 là quan trọng. Như bạn có thể thấy, không có phép thuật nào đối với
for x in iterator:
    yield x
3,
for x in iterator:
    yield x
4 hoặc toàn bộ thỏa thuận lập trình không đồng bộ này;

Hy vọng và ước mơ của tôi cho tương lai

Bây giờ tôi đã hiểu cách lập trình không đồng bộ này hoạt động trong Python, tôi muốn sử dụng nó mọi lúc. Đó là một khái niệm tuyệt vời, tốt hơn rất nhiều so với thứ mà bạn đã sử dụng chủ đề trước đây. Vấn đề là Python 3. 5 quá mới nên

for x in iterator:
    yield x
3/
for x in iterator:
    yield x
4 cũng rất mới. Điều đó có nghĩa là không có nhiều thư viện hỗ trợ lập trình bất đồng bộ như thế này. Chẳng hạn, để thực hiện các yêu cầu HTTP, bạn phải tự tạo yêu cầu HTTP bằng tay [yuck], hãy sử dụng một dự án như khung
for x in iterator:
    yield x
71 để thêm HTTP vào đầu vòng lặp sự kiện khác [trong trường hợp này là
for x in iterator:
    yield x
6] hoặc hy vọng có nhiều dự án hơn

Cá nhân tôi hy vọng các dự án như

for x in iterator:
    yield x
73 sẽ thành công để chúng tôi có sự tách biệt rõ ràng giữa việc lấy dữ liệu nhị phân từ I/O và cách chúng tôi diễn giải dữ liệu nhị phân đó. Loại trừu tượng này rất quan trọng vì hầu hết các thư viện I/O trong Python được liên kết khá chặt chẽ với cách chúng thực hiện I/O và cách chúng xử lý dữ liệu đến từ I/O. Đây là sự cố với gói
for x in iterator:
    yield x
76 trong thư viện chuẩn của Python vì gói này không có trình phân tích cú pháp HTTP mà có đối tượng kết nối thực hiện tất cả I/O cho bạn. Và nếu bạn đang hy vọng
for x in iterator:
    yield x
77 sẽ hỗ trợ lập trình không đồng bộ, thì hy vọng của bạn đã tiêu tan vì I/O đồng bộ mà
for x in iterator:
    yield x
77 sử dụng đã được đưa vào thiết kế của nó. Sự thay đổi về khả năng thực hiện lập trình không đồng bộ này mang lại cho cộng đồng Python cơ hội khắc phục sự cố mà cộng đồng này gặp phải khi không có sự trừu tượng hóa ở các lớp khác nhau của ngăn xếp mạng. Và chúng tôi có lợi thế là không khó để làm cho mã không đồng bộ chạy như thể nó đồng bộ, vì vậy các công cụ lấp đầy khoảng trống cho lập trình không đồng bộ có thể hoạt động ở cả hai thế giới

Tôi cũng hy vọng rằng Python sẽ nhận được một số hình thức hỗ trợ trong

for x in iterator:
    yield x
3 coroutines cho
for x in iterator:
    yield x
26. Có thể điều này sẽ yêu cầu một từ khóa khác [có thể giống như
for x in iterator:
    yield x
81?], nhưng thực tế là bạn thực sự không thể triển khai một hệ thống vòng lặp sự kiện chỉ với
for x in iterator:
    yield x
3 coroutine làm phiền tôi. May mắn thay, hóa ra tôi không phải là người duy nhất nghĩ điều này, và vì tác giả của PEP 492 cũng đồng ý với tôi, nên tôi nghĩ có cơ hội loại bỏ điều này

Sự kết luận

Về cơ bản,

for x in iterator:
    yield x
3 và
for x in iterator:
    yield x
4 là các trình tạo ưa thích mà chúng tôi gọi là coroutines và có một số hỗ trợ bổ sung cho những thứ được gọi là đối tượng có thể chờ đợi và biến các trình tạo đơn giản thành coroutines. Tất cả những điều này kết hợp với nhau để hỗ trợ đồng thời để chúng tôi hỗ trợ tốt hơn cho lập trình không đồng bộ trong Python. Nó tuyệt vời và dễ sử dụng hơn nhiều so với các cách tiếp cận có thể so sánh được như luồng -- Tôi đã viết một ví dụ từ đầu đến cuối về lập trình không đồng bộ dưới 100 dòng mã Python đã nhận xét -- trong khi vẫn khá linh hoạt và nhanh chóng [Câu hỏi thường gặp về đồ cổ nói rằng nó . ]. Tôi rất vui vì điều này đã xuất hiện trong Python 3 và tôi mong cộng đồng đón nhận nó và giúp tăng cường sự hỗ trợ của nó trong các thư viện và khung để tất cả chúng ta đều có thể hưởng lợi từ việc lập trình không đồng bộ trong Python

Python có không đồng bộ không?

Async IO là một thiết kế lập trình đồng thời đã nhận được sự hỗ trợ riêng trong Python , phát triển nhanh chóng từ Python 3. 4 đến 3. 7, và có lẽ hơn thế nữa. Bạn có thể đang nghĩ một cách sợ hãi, “Đồng thời, song song, phân luồng, đa xử lý. Đó là rất nhiều để nắm bắt rồi.

Python có đồng bộ hay không đồng bộ không?

Có hai loại phương thức cơ bản trong Parallels Python API. đồng bộ và không đồng bộ . Khi một phương thức đồng bộ được gọi, nó sẽ hoàn thành việc thực thi trước khi quay lại trình gọi. Một phương thức không đồng bộ bắt đầu một công việc ở chế độ nền và trả lại cho người gọi ngay lập tức.

Phiên bản Python nào có Asyncio?

asyncio yêu cầu Python 3. 3 trở lên . Mô-đun asyncio là một phần của thư viện chuẩn Python kể từ Python 3. 4. asyncio là một phần mềm miễn phí được phân phối theo giấy phép Apache phiên bản 2. 0.

Tại sao chúng tôi sử dụng async trong Python?

Mất nhiều thời gian để mở kết nối và đợi người khác hoàn thành công việc của họ. Mặt khác, async cung cấp cho bạn phương thức mở hàng nghìn kết nối cùng lúc và hoán đổi giữa từng kết nối khi chúng kết thúc và trả về kết quả .

Chủ Đề