Hướng dẫn what is synchronous and asynchronous in python? - đồng bộ và không đồng bộ trong python là gì?

Bạn đã nghe mọi người nói rằng mã python async nhanh hơn mã python "bình thường" (hoặc đồng bộ hóa)? Làm thế nào mà có thể được? Trong bài viết này, tôi sẽ cố gắng giải thích Async là gì và nó khác với mã Python bình thường như thế nào.

"Đồng bộ hóa" và "async" có nghĩa là gì?

Các ứng dụng web thường phải đối phó với nhiều yêu cầu, tất cả đều đến từ các khách hàng khác nhau và trong một khoảng thời gian ngắn. Để tránh xử lý sự chậm trễ, điều đó được coi là phải có thể xử lý một số yêu cầu cùng một lúc, một thứ thường được gọi là đồng thời. Tôi sẽ tiếp tục sử dụng các ứng dụng web làm ví dụ trong suốt bài viết này, nhưng hãy nhớ rằng có những loại ứng dụng khác cũng được hưởng lợi từ việc thực hiện nhiều nhiệm vụ đồng thời, vì vậy cuộc thảo luận này không cụ thể cho web.

Các thuật ngữ "đồng bộ hóa" và "async" đề cập đến hai cách để viết các ứng dụng sử dụng đồng thời. Các máy chủ được gọi là "đồng bộ hóa" sử dụng hỗ trợ hệ điều hành cơ bản của các luồng và quy trình để thực hiện đồng thời này. Dưới đây là một sơ đồ về cách triển khai đồng bộ hóa có thể:

Hướng dẫn what is synchronous and asynchronous in python? - đồng bộ và không đồng bộ trong python là gì?

Trong tình huống này, chúng tôi có năm khách hàng, tất cả các yêu cầu gửi đến ứng dụng. Điểm truy cập công khai cho ứng dụng này là một máy chủ web hoạt động như một bộ cân bằng tải bằng cách phân phối các yêu cầu giữa một nhóm nhân viên máy chủ, có thể được thực hiện dưới dạng quy trình, luồng hoặc kết hợp cả hai. Các công nhân thực hiện các yêu cầu khi chúng được gán cho họ bằng bộ cân bằng tải. Logic ứng dụng mà bạn có thể viết bằng khung ứng dụng web như Flask hoặc Django, sống trong các công nhân này.

Loại giải pháp này là tuyệt vời cho các máy chủ có nhiều CPU, bởi vì bạn có thể định cấu hình số lượng công nhân là bội số của CPU và với điều này, bạn có thể đạt được việc sử dụng các lõi của bạn, một thứ gì đó mà một quá trình python duy nhất Không thể làm do những hạn chế được áp đặt bởi khóa phiên dịch toàn cầu (GIL).

Về mặt nhược điểm, sơ đồ trên cho thấy rõ ràng giới hạn chính của phương pháp này là gì. Chúng tôi có năm khách hàng, nhưng chỉ có bốn công nhân. Nếu năm khách hàng này gửi tất cả các yêu cầu của họ cùng một lúc, thì bộ cân bằng tải sẽ có thể gửi tất cả trừ một người cho công nhân và yêu cầu mất cuộc đua sẽ phải ở trong một hàng trong khi nó chờ đợi một công nhân trở thành có sẵn. Vì vậy, bốn trong số năm khách hàng sẽ nhận được phản hồi của họ kịp thời, nhưng một trong số họ sẽ phải chờ đợi lâu hơn. Chìa khóa trong việc làm cho máy chủ hoạt động tốt là chọn số lượng công nhân thích hợp để ngăn chặn hoặc giảm thiểu các yêu cầu bị chặn với tải dự kiến.

Một thiết lập máy chủ không đồng bộ khó vẽ hơn, nhưng đây là cách tốt nhất của tôi:

Hướng dẫn what is synchronous and asynchronous in python? - đồng bộ và không đồng bộ trong python là gì?

Loại máy chủ này chạy trong một quy trình duy nhất được điều khiển bởi một vòng lặp. Vòng lặp là một trình quản lý tác vụ và lịch trình rất hiệu quả để tạo các tác vụ để thực hiện các yêu cầu được gửi bởi các máy khách. Không giống như nhân viên máy chủ, được sống lâu, một tác vụ Async được tạo ra bởi vòng lặp để xử lý một yêu cầu cụ thể và khi yêu cầu đó được hoàn thành, nhiệm vụ sẽ bị phá hủy. Tại bất kỳ thời điểm nào, một máy chủ Async có thể có hàng trăm hoặc thậm chí hàng ngàn tác vụ hoạt động, tất cả đều thực hiện công việc của riêng họ trong khi được quản lý bởi vòng lặp.

Bạn có thể tự hỏi làm thế nào sự đồng thời giữa các nhiệm vụ Async đạt được. Đây là phần thú vị, bởi vì một ứng dụng Async chỉ dựa vào đa nhiệm hợp tác cho việc này. Điều đó có nghĩa là gì? Khi một nhiệm vụ cần chờ một sự kiện bên ngoài, ví dụ, một phản hồi từ máy chủ cơ sở dữ liệu, thay vì chỉ chờ đợi như một nhân viên đồng bộ hóa sẽ làm, nó cho biết vòng lặp những gì nó cần chờ và sau đó trả lại quyền kiểm soát cho nó. Vòng lặp sau đó có thể tìm thấy một nhiệm vụ khác đã sẵn sàng để chạy trong khi tác vụ này bị chặn bởi cơ sở dữ liệu. Cuối cùng, cơ sở dữ liệu sẽ gửi phản hồi và tại thời điểm đó, vòng lặp sẽ xem xét nhiệm vụ đầu tiên đó sẵn sàng chạy lại và sẽ tiếp tục nó càng sớm càng tốt.

Khả năng này cho một nhiệm vụ không đồng bộ để đình chỉ và tiếp tục thực thi có thể khó hiểu trong bản tóm tắt. Để giúp bạn áp dụng điều này vào những thứ mà bạn có thể đã biết, hãy xem xét rằng trong Python, một cách để thực hiện điều này là với các từ khóa await hoặc yield, nhưng đây không phải là cách duy nhất như bạn sẽ thấy sau này.

Một ứng dụng Async chạy hoàn toàn trong một quy trình duy nhất và một luồng duy nhất, điều này không có gì đáng kinh ngạc. Tất nhiên loại đồng thời này có một số kỷ luật, vì bạn không thể có một nhiệm vụ giữ CPU quá lâu nếu không các nhiệm vụ còn lại sẽ bỏ đói. Đối với Async hoạt động, tất cả các nhiệm vụ cần tự nguyện đình chỉ và trả lại quyền kiểm soát cho vòng lặp kịp thời. Để được hưởng lợi từ kiểu Async, một ứng dụng cần phải có các nhiệm vụ thường bị chặn bởi I/O và không có quá nhiều công việc CPU. Các ứng dụng web thường phù hợp rất tốt, đặc biệt nếu họ cần xử lý một lượng lớn yêu cầu của khách hàng.

Để tối đa hóa việc sử dụng nhiều CPU khi sử dụng máy chủ Async, người ta thường tạo ra một giải pháp lai thêm bộ cân bằng tải và chạy máy chủ Async trên mỗi CPU, như trong sơ đồ sau:

Hướng dẫn what is synchronous and asynchronous in python? - đồng bộ và không đồng bộ trong python là gì?

Hai cách để làm Async trong Python

Tôi chắc chắn rằng bạn biết rằng để viết một ứng dụng Async trong Python, bạn có thể sử dụng gói Asyncio, được xây dựng trên các coroutines để thực hiện các tính năng đình chỉ và tiếp tục mà tất cả các ứng dụng không đồng bộ yêu cầu. Từ khóa yield, cùng với asyncawait mới hơn, là nền tảng mà các khả năng không đồng bộ của asyncio được xây dựng. Để vẽ một bức tranh hoàn chỉnh, có các giải pháp Async dựa trên Coroutine khác trong hệ sinh thái Python, như Trio và Curio. Ngoài ra còn có xoắn, đó là khuôn khổ coroutine lâu đời nhất của tất cả, thậm chí là trước asyncio.

Nếu bạn quan tâm đến việc viết một ứng dụng web Async, có một số khung công tác không đồng bộ dựa trên các coroutines để lựa chọn, bao gồm AioHttp, Sanic, Fastapi và Tornado.

Điều mà nhiều người không biết, đó là Coroutines chỉ là một trong hai phương pháp có sẵn trong Python để viết mã không đồng bộ. Cách thứ hai dựa trên gói gọi là Greenlet mà bạn có thể cài đặt với PIP. Greenlets tương tự như các coroutines ở chỗ chúng cũng cho phép chức năng Python đình chỉ thực thi và tiếp tục nó sau đó, nhưng cách mà chúng đạt được điều này là hoàn toàn khác nhau, điều đó có nghĩa là hệ sinh thái async trong Python bị gãy trong hai nhóm lớn .

Sự khác biệt thú vị giữa các coroutines và Greenlets để phát triển Async là cái trước yêu cầu các từ khóa và tính năng cụ thể của ngôn ngữ Python phải hoạt động, trong khi từ này thì không. Ý tôi là điều này là các ứng dụng dựa trên Coroutine cần được viết bằng cú pháp rất cụ thể, trong khi các ứng dụng dựa trên Greenlet trông giống hệt mã Python thông thường. Điều này rất tuyệt, bởi vì trong một số điều kiện nhất định, nó cho phép mã đồng bộ được thực thi không đồng bộ, một điều mà các giải pháp dựa trên coroutine như asyncio không thể làm được.

Vậy tương đương với asyncio ở phía Greenlet là gì? Tôi biết ba gói Async dựa trên Greenlets: Gevent, Eventlet và Meinheld, mặc dù gói cuối cùng là một máy chủ web hơn là một thư viện Async mục đích chung. Tất cả đều có việc thực hiện một vòng lặp Async và chúng cung cấp một tính năng "gắn khỉ" thú vị để thay thế các chức năng chặn trong thư viện tiêu chuẩn Python, chẳng hạn như các tính năng thực hiện mạng và luồng, với các phiên bản không chặn tương đương được triển khai trên đầu của Greenlets. Nếu bạn có một đoạn mã đồng bộ mà bạn muốn chạy không đồng bộ, rất có thể các gói này sẽ cho phép bạn làm điều đó.

Bạn sẽ ngạc nhiên về điều này. Theo hiểu biết của tôi, khung web duy nhất có sự hỗ trợ rõ ràng cho Greenlets không ai khác ngoài bình. Khung này tự động phát hiện khi bạn đang chạy trên máy chủ Web Greenlet và tự điều chỉnh phù hợp, mà không cần cấu hình. Khi làm điều này, bạn cần cẩn thận để không gọi các chức năng chặn, hoặc nếu bạn làm, sau đó sử dụng Khỉ để "sửa" các chức năng chặn đó.

Nhưng Flask không phải là khung duy nhất có thể được hưởng lợi từ Greenlets. Các khung web khác như Django và Chai, không có kiến ​​thức về Greenlets, cũng có thể hoạt động không đồng bộ khi kết hợp với máy chủ Web Greenlet và các chức năng chặn được gắn khỉ.

Async có nhanh hơn đồng bộ không?

Có một quan niệm sai lầm lan truyền rộng rãi liên quan đến hiệu suất của các ứng dụng đồng bộ hóa và async. Niềm tin là các ứng dụng Async nhanh hơn đáng kể so với các đối tác đồng bộ hóa của họ.

Hãy để tôi làm rõ điều này để tất cả chúng ta ở cùng một trang. Mã Python chạy với tốc độ chính xác cho dù nó được viết theo kiểu đồng bộ hoặc async. Ngoài mã, có hai yếu tố có thể ảnh hưởng đến hiệu suất của một ứng dụng đồng thời: chuyển đổi bối cảnh và khả năng mở rộng.

Context-Switching

Nỗ lực được yêu cầu để chia sẻ CPU một cách công bằng với tất cả các tác vụ đang chạy, được gọi là chuyển đổi ngữ cảnh, có thể ảnh hưởng đến hiệu suất của ứng dụng. Trong trường hợp các ứng dụng đồng bộ hóa, công việc này được thực hiện bởi hệ điều hành và về cơ bản là một hộp đen không có cấu hình hoặc tùy chọn điều chỉnh tinh chỉnh. Đối với các ứng dụng ASYNC, chuyển đổi ngữ cảnh được thực hiện bởi vòng lặp.

Việc triển khai vòng lặp mặc định được cung cấp bởi asyncio, được viết bằng Python, không được coi là rất hiệu quả. Gói UVLoop cung cấp một vòng lặp thay thế được triển khai một phần trong mã C để đạt được hiệu suất tốt hơn. Các vòng lặp sự kiện được sử dụng bởi Gevent và Meinheld cũng được viết bằng mã C. Eventlet sử dụng một vòng lặp được viết bằng Python.

Một vòng lặp Async được tối ưu hóa cao có thể hiệu quả hơn trong việc chuyển đổi ngữ cảnh so với hệ điều hành, nhưng theo kinh nghiệm của tôi, để có thể thấy một hiệu suất hữu hình mà bạn sẽ phải chạy ở mức độ đồng thời thực sự cao. Đối với hầu hết các ứng dụng, tôi không tin rằng sự khác biệt về hiệu suất giữa chuyển đổi ngữ cảnh đồng bộ và async đến bất cứ điều gì đáng kể.

Khả năng mở rộng

Điều tôi tin là nguồn gốc của huyền thoại rằng Async nhanh hơn là các ứng dụng Async thường dẫn đến việc sử dụng CPU hiệu quả hơn, do khả năng mở rộng quy mô tốt hơn nhiều và theo cách linh hoạt hơn so với đồng bộ hóa.

Xem xét những gì sẽ xảy ra với máy chủ đồng bộ hóa được hiển thị trong sơ đồ ở trên nếu nhận được một trăm yêu cầu cùng một lúc. Máy chủ này không thể xử lý nhiều hơn bốn yêu cầu cùng một lúc, vì vậy hầu hết các yêu cầu đó sẽ ở trong một hàng đợi trong một thời gian trước khi họ có thể được chỉ định một công nhân.

Tương phản rằng với máy chủ Async, sẽ ngay lập tức tạo ra một trăm tác vụ (hoặc 25 trong mỗi bốn công nhân Async nếu sử dụng mô hình lai). Với một máy chủ Async, tất cả các yêu cầu sẽ bắt đầu xử lý mà không phải chờ đợi (mặc dù công bằng, có thể có những nút thắt khác trên đường làm chậm mọi thứ, chẳng hạn như giới hạn về số lượng kết nối cơ sở dữ liệu hoạt động).

Nếu hàng trăm tác vụ này sử dụng CPU, thì các giải pháp đồng bộ hóa và async sẽ có hiệu suất tương tự, vì tốc độ mà CPU chạy được cố định, tốc độ thực thi của Python luôn giống nhau và công việc được thực hiện bởi Ứng dụng cũng bằng nhau. Nhưng nếu các tác vụ cần thực hiện nhiều hoạt động I/O, thì máy chủ đồng bộ hóa có thể không thể đạt được việc sử dụng CPU cao chỉ với bốn yêu cầu đồng thời. Máy chủ Async, ở phía bên kia, chắc chắn sẽ tốt hơn trong việc giữ cho CPU bận rộn vì nó chạy tất cả hàng trăm yêu cầu cùng một lúc.

Bạn có thể tự hỏi tại sao bạn không thể chạy một trăm công nhân đồng bộ, để hai máy chủ có cùng một sự đồng thời. Hãy xem xét rằng mỗi công nhân cần phải có trình thông dịch Python riêng với tất cả các tài nguyên liên quan đến nó, cộng với một bản sao riêng của ứng dụng với tài nguyên riêng. Kích thước của máy chủ và ứng dụng của bạn sẽ xác định có bao nhiêu phiên bản công nhân bạn có thể chạy, nhưng nói chung số này không cao lắm. Các nhiệm vụ không đồng bộ, ở phía bên kia, cực kỳ nhẹ và tất cả đều chạy trong bối cảnh của một quy trình công nhân duy nhất, vì vậy chúng có một lợi thế rõ ràng.

Giữ tất cả những điều này trong tâm trí, chúng ta có thể nói rằng Async có thể nhanh hơn đồng bộ hóa cho một kịch bản nhất định khi:

  • Có tải cao (không có tải cao, không có lợi thế khi có quyền truy cập vào đồng thời cao)
  • Các tác vụ bị ràng buộc I/O (nếu các tác vụ bị ràng buộc CPU, sau đó đồng thời trên số lượng CPU không giúp được gì)
  • Bạn nhìn vào số lượng yêu cầu trung bình xử lý trên mỗi đơn vị thời gian. Nếu bạn nhìn vào thời gian xử lý yêu cầu cá nhân, bạn sẽ không thấy sự khác biệt lớn và Async thậm chí có thể chậm hơn một chút do có các nhiệm vụ đồng thời hơn cạnh tranh cho CPU.

Sự kết luận

Tôi hy vọng bài viết này sẽ xóa một số sự nhầm lẫn và hiểu lầm về mã Async. Hai điều quan trọng mà tôi hy vọng bạn nhớ là:

  • Một ứng dụng Async sẽ chỉ làm tốt hơn so với đồng bộ tương đương dưới tải cao.
  • Nhờ Greenlets, có thể được hưởng lợi từ Async ngay cả khi bạn viết mã bình thường và sử dụng các khung truyền thống như Flask hoặc Django.

Nếu bạn muốn hiểu thêm chi tiết về cách thức hoạt động của các hệ thống không đồng bộ, hãy xem bài thuyết trình Pycon của tôi Python không đồng bộ cho người mới bắt đầu hoàn chỉnh trên YouTube.

Bạn có bất kỳ câu hỏi kéo dài liên quan đến sự khác biệt giữa đồng bộ hóa và async? Hãy cho tôi biết dưới đây trong các ý kiến!

Không đồng bộ trong Python là gì?

Với lập trình không đồng bộ, chúng ta có thể sử dụng thời gian trễ theo yêu cầu của hoạt động để xử lý và trả về kết quả để tiếp tục xử lý các tác vụ khác. Trước đây chúng tôi đã thảo luận về khái niệm đồng thời và việc sử dụng của nó trong Python. Kiểm tra nó ở đây, nếu bạn thích.use the lag time required by the operation to process and return a result to continue processing other tasks. We have previously discussed the concept of concurrency and its usage in Python. Check it out here, if you like.

Lập trình đồng bộ trong Python là gì?

Một chương trình đồng bộ được thực hiện từng bước một.Ngay cả với phân nhánh, vòng lặp và các cuộc gọi chức năng có điều kiện, bạn vẫn có thể nghĩ về mã về mặt thực hiện một bước thực thi tại một thời điểm.Khi mỗi bước hoàn tất, chương trình chuyển sang bước tiếp theo.executed one step at a time. Even with conditional branching, loops and function calls, you can still think about the code in terms of taking one execution step at a time. When each step is complete, the program moves on to the next one.

Đồng bộ vs không đồng bộ là gì?

Sự khác biệt chính giữa giao tiếp đồng bộ và không đồng bộ là giao tiếp đồng bộ được lên lịch, các tương tác thời gian thực qua điện thoại, video hoặc trực tiếp. Giao tiếp đồng bộ hóa xảy ra vào thời gian của bạn và không cần lập lịch.synchronous communications are scheduled, real-time interactions by phone, video, or in-person. Asynchronous communication happens on your own time and doesn't need scheduling.

Python có phải là một ngôn ngữ đồng bộ không?

Hầu hết các ngôn ngữ phía máy chủ như Python, C#, Java và PHP thực thi mã không phụ thuộc, do đó, một dòng hoặc toàn bộ khối thành công phụ thuộc vào sự thành công của ngôn ngữ đi trước nó.Điều này có nghĩa là tất cả chúng đồng bộ theo mặc định.they're all synchronous by default.