Trăn đồng thời
Có nhiều lý do khiến ứng dụng của bạn có thể bị chậm. Đôi khi điều này là do thiết kế thuật toán kém hoặc lựa chọn sai cấu trúc dữ liệu. Tuy nhiên, đôi khi, đó là do các yếu tố nằm ngoài tầm kiểm soát của chúng tôi, chẳng hạn như hạn chế về phần cứng hoặc các vấn đề về kết nối mạng. Đó là nơi đồng thời và song song phù hợp. Chúng cho phép các chương trình của bạn thực hiện nhiều việc cùng một lúc, đồng thời hoặc bằng cách lãng phí ít thời gian nhất có thể để chờ đợi các tác vụ bận rộn Show Cho dù bạn đang xử lý các tài nguyên web bên ngoài, đọc và ghi vào nhiều tệp hay cần sử dụng một hàm chuyên sâu về tính toán nhiều lần với các tham số khác nhau, thì bài viết này sẽ giúp bạn tối đa hóa hiệu quả và tốc độ mã của mình Đầu tiên, chúng ta sẽ tìm hiểu xem đồng thời và song song là gì và cách chúng phù hợp với lĩnh vực Python bằng cách sử dụng các thư viện tiêu chuẩn như luồng, đa xử lý và asyncio. Phần cuối cùng của bài viết này sẽ so sánh việc triển khai Python của ________ 04/________ 05 với cách các ngôn ngữ khác đã triển khai chúng Bạn có thể tìm thấy tất cả các mã ví dụ từ bài viết này trong repo concurrency-parallelism-and-asyncio trên GitHub
mục tiêuĐến cuối bài viết này, bạn sẽ có thể trả lời các câu hỏi sau
đồng thời là gì? Một định nghĩa hiệu quả cho đồng thời là "có thể thực hiện nhiều tác vụ cùng một lúc". Tuy nhiên, đây là một chút sai lệch, vì các tác vụ có thể thực sự được thực hiện hoặc không thực sự được thực hiện cùng một lúc. Thay vào đó, một quy trình có thể bắt đầu, sau đó khi nó đang đợi một hướng dẫn cụ thể kết thúc, hãy chuyển sang một tác vụ mới, chỉ quay lại khi nó không còn chờ nữa. Khi một nhiệm vụ hoàn thành, nó lại chuyển sang nhiệm vụ chưa hoàn thành cho đến khi tất cả chúng được thực hiện. Các tác vụ bắt đầu không đồng bộ, được thực hiện không đồng bộ và sau đó kết thúc không đồng bộ Nếu điều đó làm bạn bối rối, thay vào đó hãy nghĩ về một phép loại suy. Giả sử bạn muốn tạo một BLT. Đầu tiên, bạn sẽ muốn ném thịt xông khói vào chảo ở nhiệt độ trung bình thấp. Trong khi nấu thịt xông khói, bạn có thể lấy cà chua và rau diếp ra và bắt đầu sơ chế (rửa và cắt) chúng. Trong khi đó, bạn tiếp tục kiểm tra và thỉnh thoảng lật miếng thịt xông khói của mình Tại thời điểm này, bạn đã bắt đầu một nhiệm vụ, sau đó bắt đầu và hoàn thành hai nhiệm vụ khác trong thời gian chờ đợi, tất cả trong khi bạn vẫn đang đợi ở nhiệm vụ đầu tiên Cuối cùng, bạn đặt bánh mì của bạn trong một máy nướng bánh mì. Trong khi nướng, bạn tiếp tục kiểm tra thịt xông khói của mình. Khi các miếng đã hoàn thành, bạn lấy chúng ra và bày lên đĩa. Sau khi nướng xong bánh mì, bạn phết lên bánh sandwich mà bạn chọn, sau đó bạn có thể bắt đầu xếp lớp trên cà chua, rau diếp, và sau đó, sau khi nấu xong, là thịt xông khói của bạn. Chỉ sau khi mọi thứ đã được nấu chín, chuẩn bị và xếp lớp, bạn mới có thể đặt miếng bánh mì nướng cuối cùng lên bánh sandwich của mình, cắt lát (tùy chọn) và ăn Bởi vì nó yêu cầu bạn thực hiện nhiều nhiệm vụ cùng một lúc, nên việc tạo BLT vốn dĩ là một quá trình đồng thời, ngay cả khi bạn không tập trung hoàn toàn vào từng nhiệm vụ đó cùng một lúc. Đối với tất cả ý định và mục đích, trong phần tiếp theo, chúng tôi sẽ đề cập đến hình thức tương tranh này chỉ là "tương tranh. " Chúng ta sẽ phân biệt nó sau trong bài viết này Vì lý do này, tính đồng thời rất phù hợp cho các quy trình sử dụng nhiều I/O -- các tác vụ liên quan đến việc chờ yêu cầu web hoặc thao tác đọc/ghi tệp Trong Python, có một số cách khác nhau để đạt được đồng thời. Đầu tiên chúng ta sẽ xem xét thư viện luồng
Để làm việc với luồng trong Python, lần nhập duy nhất bạn cần là 6, nhưng với ví dụ này, tôi cũng đã nhập 7 để làm việc với các yêu cầu HTTP, 8 để xác định thời gian hoàn thành các chức năng và 9 để dễ dàng chuyển đổi
Hãy bắt đầu với một chức năng đơn giản
Kiểm tra mã ở trên, chúng tôi đang yêu cầu API Genrenator, tải phản hồi JSON của nó (một thể loại nhạc ngẫu nhiên), in nó, sau đó ghi nó vào một tệp
Điều chúng tôi thực sự quan tâm là phần tiếp theo, nơi diễn ra phân luồng thực tế
Đầu tiên chúng ta bắt đầu với một danh sách. Sau đó, chúng tôi tiến hành lặp lại năm lần, mỗi lần tạo một chuỗi mới. Tiếp theo, chúng tôi bắt đầu từng luồng, nối nó vào danh sách "luồng" của chúng tôi, sau đó lặp lại danh sách của chúng tôi lần cuối để tham gia từng luồng Giải trình. Tạo chủ đề trong Python thật dễ dàng Để tạo một chủ đề mới, hãy sử dụng 0. Bạn có thể chuyển vào đó kwarg (đối số từ khóa) 1 với giá trị của bất kỳ chức năng nào bạn muốn chạy trên chuỗi đó. Nhưng chỉ chuyển vào tên của hàm, không phải giá trị của nó (có nghĩa là, đối với mục đích của chúng tôi, 2 chứ không phải 3). Để chuyển các đối số, hãy chuyển vào "kwargs" (lấy lệnh của kwargs của bạn) hoặc "args" (lấy một lần lặp có chứa các đối số của bạn -- trong trường hợp này là một danh sách)Tuy nhiên, tạo một chủ đề không giống như bắt đầu một chủ đề. Để bắt đầu chủ đề của bạn, hãy sử dụng 4. Bắt đầu một luồng có nghĩa là "bắt đầu thực hiện nó. "Cuối cùng, khi chúng tôi tham gia chuỗi với 5, tất cả những gì chúng tôi đang làm là đảm bảo chuỗi đã kết thúc trước khi tiếp tục với mã của chúng tôichủ đềNhưng chính xác những gì là một chủ đề? Chuỗi là một cách cho phép máy tính của bạn chia nhỏ một quy trình/chương trình thành nhiều phần nhẹ thực thi song song. Hơi khó hiểu, việc triển khai luồng giới hạn tiêu chuẩn của Python chỉ có thể thực thi một luồng tại một thời điểm do một thứ gọi là Khóa phiên dịch toàn cầu (GIL). GIL là cần thiết vì quản lý bộ nhớ của CPython (triển khai mặc định của Python) không an toàn cho luồng. Do giới hạn này, luồng trong Python là đồng thời, nhưng không song song. Để giải quyết vấn đề này, Python có một mô-đun 6 riêng biệt không bị giới hạn bởi GIL, mô-đun này tạo ra các quy trình riêng biệt, cho phép thực thi song song mã của bạn. Sử dụng mô-đun 6 gần giống như sử dụng mô-đun 6
Chúng ta sẽ sớm tìm hiểu sâu hơn về đa xử lý trong Python Trước khi chúng tôi chỉ ra khả năng cải thiện tốc độ đối với mã không theo luồng, tôi đã tự do tạo một phiên bản không theo luồng của cùng một chương trình (một lần nữa, có sẵn trên GitHub). Thay vì tạo một luồng mới và nối từng luồng, thay vào đó, nó gọi 2 trong vòng lặp for lặp lại năm lầnĐể so sánh các điểm chuẩn tốc độ, tôi cũng đã nhập thư viện 8 để tính thời gian thực thi các tập lệnh của chúng tôi 9Khi chạy tập lệnh, chúng tôi thấy rằng máy tính của tôi mất khoảng 1. 49 giây (cùng với các thể loại nhạc cổ điển như "dutch hat industrialtune"). Không tệ lắm Bây giờ, hãy chạy phiên bản sử dụng luồng
Điều đầu tiên có thể nổi bật với bạn là các chức năng không được hoàn thành theo thứ tự. 2 - 0 - 4 - 1 - 3 Điều này là do bản chất không đồng bộ của luồng. khi một chức năng chờ đợi, một chức năng khác bắt đầu, v.v. Vì chúng tôi có thể tiếp tục thực hiện các tác vụ trong khi chờ người khác hoàn thành (do hoạt động kết nối mạng hoặc tệp I/O), bạn cũng có thể nhận thấy rằng chúng tôi đã cắt giảm khoảng một nửa thời gian của mình. 0. 77 giây. Mặc dù hiện tại điều này có vẻ không nhiều, nhưng thật dễ dàng để tưởng tượng trường hợp thực tế khi xây dựng một ứng dụng web cần ghi nhiều dữ liệu hơn vào một tệp hoặc tương tác với các dịch vụ web phức tạp hơn nhiều Vì vậy, nếu threading là tuyệt vời, tại sao chúng ta không kết thúc bài viết ở đây? Bởi vì có nhiều cách tốt hơn để thực hiện các tác vụ đồng thời không đồng bộHãy xem một ví dụ sử dụng asyncio. Đối với phương pháp này, chúng tôi sẽ cài đặt aiohttp bằng cách sử dụng 11. Điều này sẽ cho phép chúng tôi thực hiện các yêu cầu không chặn và nhận phản hồi bằng cách sử dụng cú pháp 4/ 5 sẽ sớm được giới thiệu. Nó cũng có thêm lợi ích là chức năng chuyển đổi phản hồi JSON mà không cần nhập thư viện 9. Chúng tôi cũng sẽ cài đặt và nhập aiofiles, cho phép các thao tác với tệp không bị chặn. Ngoài 15 và 16, hãy nhập 17, đi kèm với thư viện chuẩn Python"Không chặn" có nghĩa là một chương trình sẽ cho phép các luồng khác tiếp tục chạy trong khi chờ đợi. Điều này trái ngược với mã "chặn", dừng hoàn toàn việc thực thi chương trình của bạn. Hoạt động I/O bình thường, đồng bộ bị giới hạn này
Khi chúng tôi đã nhập xong, hãy xem phiên bản không đồng bộ của hàm 2 từ ví dụ asyncio của chúng tôi________số 8 Đối với những người không quen thuộc với cú pháp 4/ 5 có thể tìm thấy trong nhiều ngôn ngữ hiện đại khác, 4 tuyên bố rằng một hàm, vòng lặp 32 hoặc câu lệnh 33 phải được sử dụng không đồng bộ. Để gọi một hàm không đồng bộ, bạn phải sử dụng từ khóa 5 từ một hàm không đồng bộ khác hoặc gọi trực tiếp 35 từ vòng lặp sự kiện, có thể lấy từ 36 -- i. e. , 37Ngoài ra
Vòng lặp sự kiệnCác vòng lặp sự kiện là các cấu trúc vốn có của lập trình không đồng bộ cho phép thực hiện các tác vụ không đồng bộ. Khi bạn đang đọc bài viết này, tôi có thể yên tâm cho rằng bạn có thể không quá quen thuộc với khái niệm này. Tuy nhiên, ngay cả khi bạn chưa bao giờ viết một ứng dụng không đồng bộ, bạn vẫn có kinh nghiệm với các vòng lặp sự kiện mỗi khi sử dụng máy tính. Cho dù máy tính của bạn đang lắng nghe đầu vào bàn phím, bạn đang chơi trò chơi nhiều người chơi trực tuyến hay bạn đang duyệt Reddit trong khi đang sao chép tệp ở chế độ nền, vòng lặp sự kiện là động lực giúp mọi thứ hoạt động trơn tru và hiệu quả. Về bản chất thuần túy nhất, vòng lặp sự kiện là một quá trình chờ đợi các yếu tố kích hoạt và sau đó thực hiện các hành động (được lập trình) cụ thể sau khi các yếu tố kích hoạt đó được đáp ứng. Chúng thường trả về một số loại "lời hứa" (cú pháp JavaScript) hoặc "tương lai" (cú pháp Python) để biểu thị rằng một tác vụ đã được thêm vào. Khi tác vụ kết thúc, lời hứa hoặc tương lai trả về một giá trị được truyền lại từ hàm được gọi (giả sử hàm đó trả về một giá trị) Ý tưởng thực hiện một chức năng để đáp lại một chức năng khác được gọi là "gọi lại. "
Đây là hướng dẫn về chức năng của chúng tôi Chúng tôi đang sử dụng 38 để mở phiên máy khách của mình một cách không đồng bộ. Lớp 21 là lớp cho phép chúng tôi thực hiện các yêu cầu HTTP và duy trì kết nối với nguồn mà không chặn việc thực thi mã của chúng tôi. Sau đó, chúng tôi thực hiện một yêu cầu không đồng bộ với API Genrenator và chờ phản hồi JSON (một thể loại nhạc ngẫu nhiên). Trong dòng tiếp theo, chúng tôi sử dụng lại 38 với thư viện 16 để mở một tệp mới không đồng bộ để ghi thể loại mới của chúng tôi vào. Chúng tôi in thể loại, sau đó ghi nó vào tệpKhông giống như các tập lệnh Python thông thường, lập trình với asyncio thực thi khá nhiều* bằng cách sử dụng một số loại chức năng "chính"
Điều này là do bạn cần sử dụng từ khóa "không đồng bộ" để sử dụng cú pháp "chờ đợi" và cú pháp "chờ đợi" là cách duy nhất để thực sự chạy các chức năng không đồng bộ khác Đây là chức năng chính của chúng tôi 1Như bạn có thể thấy, chúng tôi đã khai báo nó với "async. " Sau đó, chúng tôi tạo một danh sách trống có tên là "tác vụ" để chứa các tác vụ không đồng bộ của chúng tôi (các lệnh gọi tới Genrenator và tệp I/O của chúng tôi). Chúng tôi thêm các nhiệm vụ của mình vào danh sách của mình, nhưng chúng chưa thực sự chạy. Các cuộc gọi không thực sự được thực hiện cho đến khi chúng tôi lên lịch với 24. Điều này chạy tất cả các nhiệm vụ trong danh sách của chúng tôi và đợi chúng hoàn thành trước khi tiếp tục với phần còn lại của chương trình của chúng tôi. Cuối cùng, chúng tôi sử dụng 25 để chạy chức năng "chính" của mình. Hàm 26 là điểm bắt đầu cho chương trình của chúng ta và nó thường chỉ được gọi một lần cho mỗi quy trình
Và đó là tất cả những gì chúng ta cần làm. Bây giờ, hãy chạy chương trình của chúng tôi (nguồn bao gồm chức năng định thời giống nhau của các ví dụ luồng và đồng bộ) 3chúng tôi thấy nó thậm chí còn nhanh hơn. Và nói chung, phương thức asyncio sẽ luôn nhanh hơn một chút so với phương thức luồng. Điều này là do khi chúng tôi sử dụng cú pháp "chờ đợi", về cơ bản chúng tôi nói với chương trình của mình "chờ đã, tôi sẽ quay lại ngay", nhưng chương trình của chúng tôi theo dõi xem chúng tôi mất bao lâu để hoàn thành công việc đang làm. Khi chúng tôi hoàn tất, chương trình của chúng tôi sẽ biết và sẽ quay lại ngay khi có thể. Phân luồng trong Python cho phép tính không đồng bộ, nhưng về mặt lý thuyết, chương trình của chúng tôi có thể bỏ qua các luồng khác nhau có thể chưa sẵn sàng, gây lãng phí thời gian nếu có các luồng sẵn sàng tiếp tục chạy Vậy khi nào tôi nên sử dụng luồng và khi nào tôi nên sử dụng asyncio? Khi bạn đang viết mã mới, hãy sử dụng asyncio. Nếu bạn cần giao tiếp với các thư viện cũ hơn hoặc những thư viện không hỗ trợ asyncio, bạn có thể sử dụng luồng tốt hơn Kiểm tra asyncio với pytestHóa ra việc kiểm tra các chức năng không đồng bộ với pytest cũng dễ như kiểm tra các chức năng đồng bộ. Chỉ cần cài đặt gói pytest-asyncio với 11, đánh dấu các bài kiểm tra của bạn bằng từ khóa 4 và áp dụng một trình trang trí cho phép 41 biết nó không đồng bộ. 42. Hãy xem một ví dụĐầu tiên, hãy viết một hàm async tùy ý trong một tệp có tên hello_asyncio. py 2Hàm nhận một đối số chuỗi đơn. 43. Khi đảm bảo rằng 43 là một chuỗi có độ dài lớn hơn một, chức năng của chúng tôi ngủ không đồng bộ trong hai giây, sau đó in 45 ra bàn điều khiển
Bây giờ hãy kiểm tra nó với pytest. Trong cùng thư mục với hello_asyncio. py, tạo một tệp có tên test_hello_asyncio. py, sau đó mở nó trong trình soạn thảo văn bản yêu thích của bạn Hãy bắt đầu với hàng nhập khẩu của chúng tôi 4Sau đó, chúng tôi sẽ tạo một bài kiểm tra với đầu vào thích hợp 4Những điều cần lưu ý
Bây giờ, hãy chạy thử nghiệm của chúng tôi với tùy chọn dài dòng 42 0có vẻ tốt. Tiếp theo, chúng ta sẽ viết một vài bài kiểm tra với đầu vào xấu. Quay lại bên trong test_hello_asyncio. py, hãy tạo một lớp có tên là 43 1Một lần nữa, chúng tôi trang trí các bài kiểm tra của mình bằng 42, đánh dấu các bài kiểm tra của chúng tôi bằng cú pháp 4, sau đó gọi hàm của chúng tôi bằng 5Chạy lại các bài kiểm tra 2Không có pytest-asyncioNgoài ra để pytest-asyncio, bạn có thể tạo một vật cố định pytest tạo ra vòng lặp sự kiện asyncio 3Sau đó, thay vì sử dụng cú pháp 4/ 5, bạn tạo các bài kiểm tra của mình như cách bạn thực hiện các bài kiểm tra đồng bộ, bình thường 4
Đọc thêmNếu bạn muốn tìm hiểu thêm về những gì phân biệt việc triển khai luồng của Python so với asyncio, thì đây là một bài viết tuyệt vời từ Medium Để có các ví dụ và giải thích tốt hơn nữa về phân luồng trong Python, đây là video của Corey Schafer đi sâu hơn, bao gồm cả việc sử dụng thư viện 49Cuối cùng, để tìm hiểu sâu về chính asyncio, đây là một bài viết từ Real Python hoàn toàn dành riêng cho nó Thưởng. Một thư viện khác mà bạn có thể quan tâm có tên là Unsync, đặc biệt nếu bạn muốn dễ dàng chuyển đổi mã đồng bộ hiện tại của mình thành mã không đồng bộ. Để sử dụng nó, bạn cài đặt thư viện với pip, nhập nó với 00, sau đó trang trí bất kỳ chức năng hiện đang đồng bộ nào với 01 để làm cho nó không đồng bộ. Để đợi nó và nhận giá trị trả về của nó (bạn có thể thực hiện ở bất kỳ đâu -- không nhất thiết phải ở trong hàm không đồng bộ/không đồng bộ), chỉ cần gọi 02 sau lệnh gọi hàmsong song là gì? Song song có liên quan rất nhiều đến đồng thời. Trong thực tế, song song là một tập hợp con của đồng thời. trong khi một quy trình đồng thời thực hiện nhiều nhiệm vụ cùng một lúc cho dù chúng có được chuyển hướng hoàn toàn sự chú ý hay không, thì một quy trình song song thực hiện nhiều nhiệm vụ cùng một lúc. Một ví dụ điển hình là lái xe, nghe nhạc và ăn BLT mà chúng tôi đã thực hiện trong phần trước cùng một lúc Bởi vì chúng không đòi hỏi nhiều nỗ lực, bạn có thể thực hiện tất cả chúng cùng một lúc mà không cần phải chờ đợi bất cứ điều gì hoặc chuyển hướng sự chú ý của bạn đi chỗ khác Bây giờ hãy xem cách thực hiện điều này trong Python. Chúng ta có thể sử dụng thư viện 6, nhưng thay vào đó, hãy sử dụng thư viện 49 -- nó giúp loại bỏ nhu cầu quản lý số lượng quy trình theo cách thủ công. Bởi vì lợi ích chính của đa xử lý xảy ra khi bạn thực hiện nhiều tác vụ nặng bằng cpu, nên chúng tôi sẽ tính các bình phương từ 1 triệu (1000000) đến 1 triệu và 16 (1000016)
Lần nhập duy nhất chúng tôi cần là 49 5
Vì vậy, hãy xem chức năng chính của chúng tôi, chúng tôi sử dụng khả năng hiểu danh sách để tạo danh sách từ 1 triệu đến 1 triệu và 16, chúng tôi mở một ProcessPoolExecutor đồng thời. tương lai và chúng tôi sử dụng khả năng hiểu danh sách và 08 để bắt đầu thực hiện các quy trình của mình và đưa chúng vào một danh sách có tên là "tương lai. "
Và đây là lúc sự không đồng bộ xuất hiện. Danh sách "kết quả" không thực sự chứa kết quả từ việc chạy các chức năng của chúng tôi. Thay vào đó, nó chứa "tương lai" tương tự như ý tưởng JavaScript về "lời hứa. " Để cho phép chương trình của chúng tôi tiếp tục chạy, chúng tôi lấy lại các hợp đồng tương lai này đại diện cho một trình giữ chỗ cho một giá trị. Nếu chúng tôi cố gắng in tương lai, tùy thuộc vào việc nó có chạy xong hay không, chúng tôi sẽ nhận lại trạng thái "đang chờ xử lý" hoặc "đã hoàn thành. " Sau khi hoàn thành, chúng ta có thể nhận được giá trị trả về (giả sử có) bằng cách sử dụng 10. Trong trường hợp này, var của chúng ta sẽ là "result. "Sau đó, chúng tôi lặp lại danh sách tương lai của mình, nhưng thay vì in các giá trị của chúng tôi, chúng tôi chỉ in ra "được. " Điều này chỉ là do mức độ lớn của các tính toán kết quả Cũng như trước đây, tôi đã xây dựng một tập lệnh so sánh thực hiện điều này một cách đồng bộ. Và, như trước đây, bạn có thể tìm thấy nó trên GitHub Chạy chương trình điều khiển của chúng tôi, cũng bao gồm chức năng định thời gian cho chương trình của chúng tôi, chúng tôi nhận được 6Ồ. 54. 64 giây là một khoảng thời gian khá dài. Hãy xem liệu phiên bản đa xử lý của chúng tôi có tốt hơn không 7Thời gian của chúng tôi đã được giảm đáng kể. Chúng ta đang ở khoảng 1/9 thời gian ban đầu Vậy điều gì sẽ xảy ra nếu chúng ta sử dụng phân luồng cho việc này? Tôi chắc rằng bạn có thể đoán được -- nó sẽ không nhanh hơn nhiều so với thực hiện đồng bộ. Trên thực tế, nó có thể chậm hơn vì vẫn mất một ít thời gian và công sức để tạo các chủ đề mới. Nhưng đừng hiểu ý tôi, đây là những gì chúng tôi nhận được khi thay thế 11 bằng 09 8Như tôi đã đề cập trước đó, phân luồng cho phép các ứng dụng của bạn tập trung vào các tác vụ mới trong khi các tác vụ khác đang chờ. Trong trường hợp này, chúng tôi không bao giờ ngồi yên. Mặt khác, đa xử lý tạo ra các dịch vụ hoàn toàn mới, thường là trên các lõi CPU riêng biệt, sẵn sàng làm bất cứ điều gì bạn yêu cầu nó hoàn toàn song song với bất kỳ điều gì khác mà tập lệnh của bạn đang thực hiện. Đây là lý do tại sao phiên bản đa xử lý chiếm khoảng 1/9 thời gian có ý nghĩa - Tôi có 8 lõi trong CPU của mình Bây giờ chúng ta đã nói về tính đồng thời và tính song song trong Python, cuối cùng chúng ta có thể thiết lập các thuật ngữ một cách thẳng thắn. Nếu bạn gặp khó khăn trong việc phân biệt giữa các thuật ngữ, bạn có thể nghĩ một cách an toàn và chính xác về các định nghĩa trước đây của chúng tôi về "song song" và "đồng thời" lần lượt là "đồng thời song song" và "đồng thời không song song" Đọc thêmReal Python có một bài viết tuyệt vời về đồng thời và song song Kỹ sư Man có một video so sánh tốt về phân luồng và đa xử lý Corey Schafer cũng có một video hay về đa xử lý theo tinh thần giống như video chia luồng của anh ấy Nếu bạn chỉ xem một video, hãy xem bài nói chuyện tuyệt vời này của Raymond Hettinger. Anh ấy làm một công việc tuyệt vời khi giải thích sự khác biệt giữa đa xử lý, phân luồng và không đồng bộ Kết hợp Asyncio với Đa xử lýĐiều gì xảy ra nếu tôi cần kết hợp nhiều thao tác I/O với các phép tính nặng? Chúng ta cũng có thể làm điều đó. Giả sử bạn cần quét 100 trang web để tìm một phần thông tin cụ thể, sau đó bạn cần lưu phần thông tin đó vào một tệp để sử dụng sau. Chúng tôi có thể phân tách sức mạnh tính toán trên từng lõi của máy tính bằng cách làm cho mỗi quy trình cạo một phần trang Đối với tập lệnh này, hãy cài đặt Beautiful Soup để giúp chúng tôi dễ dàng cạo các trang của mình. 13. Lần này chúng tôi thực sự có khá nhiều hàng nhập khẩu. Chúng đây rồi, và đây là lý do tại sao chúng tôi sử dụng chúng 9
Đầu tiên, chúng ta sẽ tạo một hàm async gửi yêu cầu tới Wikipedia để lấy lại các trang ngẫu nhiên. Chúng tôi sẽ cạo từng trang mà chúng tôi lấy lại tiêu đề của nó bằng cách sử dụng 14, sau đó chúng tôi sẽ nối nó vào một tệp nhất định; . Hàm sẽ nhận hai đối số
90Cả hai chúng tôi đang mở một aiohttp 15 không đồng bộ và tệp đầu ra của chúng tôi. Chế độ, 16, có nghĩa là thêm vào tệp và tạo tệp nếu nó chưa tồn tại. Mã hóa các chuỗi của chúng tôi dưới dạng utf-8 đảm bảo chúng tôi không gặp lỗi nếu tiêu đề của chúng tôi chứa các ký tự quốc tế. Nếu chúng tôi nhận được phản hồi về lỗi, chúng tôi sẽ nâng nó lên thay vì tiếp tục (với khối lượng yêu cầu cao, tôi đã nhận được 429 Quá nhiều yêu cầu). Chúng tôi lấy văn bản từ phản hồi của mình một cách không đồng bộ, sau đó chúng tôi phân tích cú pháp tiêu đề và không đồng bộ rồi nối nó vào tệp của chúng tôi. Sau khi chúng tôi nối tất cả các tiêu đề của mình, chúng tôi nối thêm một dòng mới. "\N"Chức năng tiếp theo của chúng tôi là chức năng chúng tôi sẽ bắt đầu với mỗi quy trình mới để cho phép chạy nó không đồng bộ 91Bây giờ cho chức năng chính của chúng tôi. Hãy bắt đầu với một số hằng số (và khai báo hàm của chúng ta) 92Và bây giờ logic 93Chúng tôi tạo một mảng để lưu trữ tương lai của mình, sau đó chúng tôi tạo một 17, đặt 18 của nó bằng số lượng lõi của chúng tôi. Chúng tôi lặp lại một phạm vi bằng số lõi của chúng tôi trừ đi 1, chạy một quy trình mới với chức năng 19 của chúng tôi. Sau đó, chúng tôi nối nó vào danh sách tương lai của chúng tôi. Lõi cuối cùng của chúng tôi có khả năng sẽ có thêm việc phải làm vì nó sẽ cạo một số trang bằng với mỗi lõi khác của chúng tôi, nhưng cũng sẽ cạo thêm một số trang bằng với phần còn lại mà chúng tôi nhận được khi chia tổng số trang của mình để cạo Đảm bảo thực sự chạy chức năng chính của bạn 94Sau khi chạy chương trình với CPU 8 nhân của tôi (cùng với mã đo điểm chuẩn) Phiên bản này (không đồng bộ với đa xử lý) 95chỉ đa xử lý 96chỉ không đồng bộ 97Hoàn toàn đồng bộ 98Tôi thực sự khá ngạc nhiên khi thấy rằng sự cải tiến của asyncio với đa xử lý thay vì chỉ đa xử lý không tốt như tôi nghĩ. Tóm tắt lại. Khi nào nên sử dụng đa xử lý so với asyncio hoặc phân luồng
Không đồng bộ/Đang chờ bằng các ngôn ngữ khác 4/ 5 và cú pháp tương tự cũng tồn tại trong các ngôn ngữ khác và trong một số ngôn ngữ đó, cách triển khai có thể khác biệt đáng kểBỌC LƯỚI. F# đến CNgôn ngữ lập trình đầu tiên (vào năm 2007) sử dụng cú pháp 4 là F# của Microsoft. Trong khi nó không sử dụng chính xác 5 để chờ lệnh gọi hàm, nó sử dụng cú pháp cụ thể như 24 và 25 cùng với các hàm 26 độc quyền có trong mô-đun 27
Sau đó, nhóm C# của họ đã xây dựng dựa trên khái niệm này và đó là nơi mà các từ khóa 4/ 5 mà chúng ta quen thuộc đã ra đời 99chạy nó trên. NET Fiddle Chúng tôi đảm bảo rằng chúng tôi là 30 vì nó bao gồm loại 31 và nói chung, loại 31 là cần thiết để chức năng không đồng bộ được chờ đợi. Điều thú vị về C# là bạn có thể làm cho chức năng chính của mình không đồng bộ chỉ bằng cách khai báo nó với 4 và bạn sẽ không gặp vấn đề gì
JavaScriptLần đầu tiên được giới thiệu trong ES6, cú pháp 4/ 5 về cơ bản là sự trừu tượng hóa các lời hứa JavaScript (tương tự như hợp đồng tương lai của Python). Tuy nhiên, không giống như Python, miễn là bạn không phải chờ đợi, bạn có thể gọi một hàm async bình thường mà không cần một hàm cụ thể như Python's 38 0Chạy nó trên JSFiddle
rỉ sétGiờ đây, Rust cũng cho phép sử dụng cú pháp 4/_______05 và nó hoạt động tương tự như Python, C# và JavaScript 1Chạy nó trên Rust Play Để sử dụng các chức năng không đồng bộ, trước tiên chúng tôi phải thêm 43 vào Cargo của chúng tôi. toml. Sau đó, chúng tôi nhập chức năng 44 với 45 - 44 là cần thiết để chạy chức năng không đồng bộ của chúng tôi từ chức năng 47 đồng bộ của chúng tôi
ĐiThay vì cú pháp truyền thống 4/ 5 vốn có của tất cả các ngôn ngữ trước đây mà chúng ta đã đề cập, Go sử dụng "goroutines" và "channels". " Bạn có thể coi một kênh tương tự như một tương lai của Python. Trong Go, bạn thường gửi một kênh làm đối số cho một hàm, sau đó sử dụng 52 để chạy đồng thời hàm. Bất cứ khi nào bạn cần đảm bảo chức năng đã hoàn tất, bạn sử dụng cú pháp 53, bạn có thể coi đây là cú pháp 5 phổ biến hơn. Nếu goroutine của bạn (hàm bạn đang chạy không đồng bộ) có giá trị trả về, thì nó có thể được lấy theo cách này 2Chạy nó trong Go Playground
hồng ngọcTương tự như Python, Ruby cũng có giới hạn Khóa thông dịch viên toàn cầu. Những gì nó không có là đồng thời được tích hợp sẵn trong ngôn ngữ. Tuy nhiên, có một loại đá quý do cộng đồng tạo ra cho phép đồng thời trong Ruby và bạn có thể tìm thấy nguồn của nó trên GitHub JavaGiống như Ruby, Java không có cú pháp 4/ 5 tích hợp sẵn, nhưng nó có khả năng tương tranh bằng cách sử dụng mô-đun 57. Tuy nhiên, Electronic Arts đã viết một thư viện Async cho phép sử dụng 5 như một phương thức. Nó không hoàn toàn giống với Python/C#/JavaScript/Rust, nhưng nó đáng để xem xét nếu bạn là nhà phát triển Java và quan tâm đến loại chức năng nàyC++Mặc dù C++ cũng không có cú pháp 4/ 5, nhưng nó có khả năng sử dụng hợp đồng tương lai để chạy mã đồng thời bằng cách sử dụng mô-đun 61 3Chạy nó trên C++ Shell Không cần khai báo một hàm với bất kỳ từ khóa nào để biểu thị liệu nó có thể và nên chạy không đồng bộ hay không. Thay vào đó, bạn khai báo tương lai ban đầu của mình bất cứ khi nào bạn cần với 62 và đặt nó bằng 63, bao gồm tên của hàm bạn muốn thực hiện không đồng bộ cùng với bất kỳ đối số nào mà nó cần -- i. e. , 64. Để chờ đợi giá trị của tương lai, hãy sử dụng cú pháp 65 trên đó
Tóm lượcCho dù bạn đang làm việc với mạng không đồng bộ hoặc hoạt động tệp hay bạn đang thực hiện nhiều phép tính phức tạp, có một số cách khác nhau để tối đa hóa hiệu quả mã của bạn Nếu đang sử dụng Python, bạn có thể sử dụng 17 hoặc 6 để tận dụng tối đa hoạt động I/O hoặc mô-đun 6 dành cho mã sử dụng nhiều CPU
Nếu bạn đang sử dụng một ngôn ngữ lập trình khác, rất có thể cũng có triển khai 4/ 5 cho ngôn ngữ đó
|