Cách tiếp cận không đồng bộ của Microsoft là một sự thay thế tốt cho các mục đích phổ biến nhất cho lập trình đa luồng: cải thiện khả năng đáp ứng đối với các tác vụ IO.
Tuy nhiên, điều quan trọng cần nhận ra là cách tiếp cận không đồng bộ hoàn toàn không có khả năng cải thiện hiệu năng hoặc cải thiện khả năng phản hồi đối với các tác vụ nặng của CPU.
Đa luồng cho phản hồi
Đa luồng cho khả năng đáp ứng là cách truyền thống để giữ cho chương trình phản hồi nhanh trong các tác vụ IO nặng hoặc các tác vụ tính toán nặng. Bạn lưu các tệp trên một luồng nền, để người dùng có thể tiếp tục công việc của họ mà không phải đợi ổ cứng hoàn thành nhiệm vụ. Luồng IO thường chặn chờ một phần của ghi để kết thúc, vì vậy các chuyển đổi ngữ cảnh là thường xuyên.
Tương tự, khi thực hiện một phép tính phức tạp, bạn muốn cho phép chuyển đổi ngữ cảnh thường xuyên để giao diện người dùng có thể vẫn phản hồi và người dùng không nghĩ rằng chương trình đã bị hỏng.
Nói chung, mục tiêu ở đây không phải là để có nhiều luồng chạy trên các CPU khác nhau. Thay vào đó, chúng tôi chỉ quan tâm đến việc chuyển đổi ngữ cảnh xảy ra giữa tác vụ nền chạy dài và giao diện người dùng, để UI có thể cập nhật và phản hồi cho người dùng trong khi tác vụ nền đang chạy. Nói chung, UI sẽ không chiếm nhiều năng lượng CPU và khung luồng hoặc HĐH thường sẽ quyết định chạy chúng trên cùng một CPU.
Chúng tôi thực sự mất hiệu năng tổng thể do chi phí chuyển đổi ngữ cảnh thêm, nhưng chúng tôi không quan tâm vì hiệu suất của CPU không phải là mục tiêu của chúng tôi. Chúng tôi biết rằng chúng tôi thường có nhiều năng lượng CPU hơn mức chúng tôi cần, và vì vậy mục tiêu của chúng tôi liên quan đến đa luồng là hoàn thành nhiệm vụ cho người dùng mà không lãng phí thời gian của người dùng.
Phương án "Không đồng bộ"
"Cách tiếp cận không đồng bộ" thay đổi hình ảnh này bằng cách cho phép chuyển đổi ngữ cảnh trong một luồng. Điều này đảm bảo rằng tất cả các tác vụ của chúng tôi sẽ chạy trên một CPU và có thể cung cấp một số cải tiến hiệu suất khiêm tốn về mặt tạo / dọn dẹp luồng ít hơn và ít chuyển đổi ngữ cảnh thực hơn giữa các luồng.
Thay vì tạo một luồng mới để chờ nhận tài nguyên mạng (ví dụ như tải xuống một hình ảnh), một async
phương thức được sử dụng, đó await
là hình ảnh trở nên khả dụng, và trong khi đó, sẽ mang lại phương thức gọi.
Ưu điểm chính ở đây là bạn không phải lo lắng về các vấn đề luồng như tránh bế tắc, vì bạn hoàn toàn không sử dụng khóa và đồng bộ hóa, và có ít công việc hơn cho lập trình viên thiết lập luồng nền và quay lại trên luồng UI khi kết quả quay lại để cập nhật UI an toàn.
Tôi đã không nhìn quá sâu vào các chi tiết kỹ thuật, nhưng ấn tượng của tôi là việc quản lý tải xuống bằng hoạt động CPU nhẹ thỉnh thoảng trở thành một nhiệm vụ không phải cho một luồng riêng biệt, mà là một thứ giống như một nhiệm vụ trên hàng đợi sự kiện UI và khi tải xuống hoàn tất, phương thức không đồng bộ được nối lại từ hàng đợi sự kiện đó. Nói cách khác, await
có nghĩa là một cái gì đó giống như "kiểm tra xem kết quả tôi cần có sẵn hay không, nếu không, hãy đưa tôi trở lại hàng đợi nhiệm vụ của chủ đề này".
Lưu ý rằng phương pháp này sẽ không giải quyết được vấn đề của một tác vụ chuyên sâu về CPU: không có dữ liệu nào để chờ đợi, vì vậy chúng tôi không thể nhận được các chuyển đổi ngữ cảnh mà chúng tôi cần xảy ra mà không tạo ra một luồng xử lý nền thực tế. Tất nhiên, vẫn có thể thuận tiện khi sử dụng phương thức không đồng bộ để bắt đầu luồng nền và trả về kết quả, trong một chương trình sử dụng một cách phổ biến phương pháp không đồng bộ.
Đa luồng cho hiệu suất
Vì bạn nói về "hiệu suất", tôi cũng muốn thảo luận về cách đa luồng có thể được sử dụng để tăng hiệu suất, điều này hoàn toàn không thể với cách tiếp cận không đồng bộ đơn luồng.
Khi bạn thực sự ở trong một tình huống mà bạn không có đủ năng lượng CPU trên một CPU và muốn sử dụng đa luồng cho hiệu năng, điều đó thực sự rất khó thực hiện. Mặt khác, nếu một CPU không đủ sức mạnh xử lý, thì đó cũng thường là giải pháp duy nhất có thể cho phép chương trình của bạn thực hiện những gì bạn muốn thực hiện trong một khung thời gian hợp lý, đó là điều làm cho công việc trở nên đáng giá.
Song song tầm thường
Tất nhiên, đôi khi có thể dễ dàng tăng tốc thực sự từ đa luồng.
Nếu bạn tình cờ có một số lượng lớn các tác vụ chuyên sâu tính toán độc lập (nghĩa là các tác vụ có dữ liệu đầu vào và đầu ra rất nhỏ đối với các phép tính phải được thực hiện để xác định kết quả), thì bạn thường có thể tăng tốc đáng kể bằng cách tạo một nhóm các luồng (có kích thước phù hợp dựa trên số lượng CPU có sẵn) và có một luồng chính phân phối công việc và thu thập kết quả.
Đa luồng thực tế cho hiệu suất
Tôi không muốn đưa mình về phía trước quá nhiều chuyên gia, nhưng ấn tượng của tôi là, nói chung, đa luồng thực tế nhất cho hiệu suất xảy ra ngày nay là tìm kiếm các vị trí trong một ứng dụng có tính song song tầm thường và sử dụng nhiều luồng để gặt hái những lợi ích.
Như với bất kỳ tối ưu hóa nào, tốt hơn hết là tối ưu hóa sau khi bạn định hình hiệu suất chương trình của mình và xác định các điểm nóng: dễ dàng làm chậm chương trình bằng cách quyết định tùy ý rằng phần này sẽ chạy trong một luồng và phần đó trong phần khác, mà không đầu tiên xác định xem cả hai phần có chiếm một phần đáng kể thời gian của CPU hay không.
Một luồng bổ sung có nghĩa là chi phí thiết lập / phá vỡ nhiều hơn và chuyển đổi ngữ cảnh nhiều hơn hoặc chi phí liên lạc giữa các CPU nhiều hơn. Nếu nó không hoạt động đủ để bù cho các chi phí đó nếu trên một CPU riêng biệt và không cần phải là một luồng riêng biệt vì lý do đáp ứng, nó sẽ làm mọi thứ chậm lại không có lợi.
Tìm kiếm các nhiệm vụ có ít phụ thuộc lẫn nhau và đang chiếm một phần đáng kể trong thời gian chạy chương trình của bạn.
Nếu chúng không có sự phụ thuộc lẫn nhau, thì đó là trường hợp song song không đáng kể, bạn có thể dễ dàng thiết lập từng chuỗi với một chuỗi và tận hưởng các lợi ích.
Nếu bạn có thể tìm thấy các tác vụ với sự phụ thuộc lẫn nhau hạn chế, do đó việc khóa và đồng bộ hóa để trao đổi thông tin không làm chậm đáng kể, thì đa luồng có thể tăng tốc, miễn là bạn cẩn thận để tránh các nguy cơ bế tắc do lỗi logic khi đồng bộ hóa hoặc kết quả không chính xác do không đồng bộ hóa khi cần thiết.
Ngoài ra, một số ứng dụng phổ biến hơn cho đa luồng không (tìm hiểu) để tăng tốc thuật toán được xác định trước, mà thay vào đó là ngân sách lớn hơn cho thuật toán mà họ dự định viết: nếu bạn đang viết một công cụ trò chơi và AI của bạn phải đưa ra quyết định trong tốc độ khung hình của bạn, bạn thường có thể cung cấp cho AI của mình ngân sách chu kỳ CPU lớn hơn nếu bạn có thể cung cấp cho CPU của chính nó.
Tuy nhiên, hãy chắc chắn để lập hồ sơ các chủ đề và đảm bảo rằng họ đang làm đủ công việc để bù đắp chi phí tại một số điểm.
Thuật toán song song
Ngoài ra còn có rất nhiều vấn đề có thể được xử lý bằng cách sử dụng nhiều bộ xử lý, nhưng nó quá nguyên khối để chỉ đơn giản là phân chia giữa các CPU.
Các thuật toán song song phải được phân tích cẩn thận cho thời gian hoạt động lớn nhất của chúng đối với thuật toán không song song tốt nhất hiện có, vì rất dễ dàng cho chi phí truyền thông giữa các CPU để loại bỏ bất kỳ lợi ích nào từ việc sử dụng nhiều CPU. Nói chung, họ phải sử dụng ít giao tiếp giữa các CPU (theo thuật ngữ big-O) hơn là họ sử dụng các tính toán trên mỗi CPU.
Hiện tại, nó vẫn chủ yếu là một không gian cho nghiên cứu học thuật, một phần là do yêu cầu phân tích phức tạp, một phần vì sự song song tầm thường khá phổ biến, một phần vì chúng ta chưa có quá nhiều lõi CPU trên máy tính của chúng ta. không thể giải quyết trong khung thời gian hợp lý trên một CPU có thể được giải trong khung thời gian hợp lý bằng cách sử dụng tất cả các CPU của chúng tôi.