2012-02-26 33 views
63

Trong các ngày công nghệ tại Hà Lan, Steve Sanderson đã trình bày về C#5, ASP.NET MVC 4, and asynchronous Web.Tại sao sử dụng các yêu cầu không đồng bộ thay vì sử dụng một luồng lớn hơn?

Ông giải thích rằng khi yêu cầu mất nhiều thời gian để hoàn thành, tất cả các chủ đề từ threadpool trở nên bận rộn và yêu cầu mới phải đợi. Máy chủ không thể xử lý tải và mọi thứ đều chậm lại. Sau đó, ông đã cho thấy cách sử dụng các yêu cầu web async cải thiện hiệu suất vì công việc sau đó được giao cho một chủ đề khác và threadpool có thể phản hồi nhanh chóng các yêu cầu mới đến. Ông thậm chí còn giới thiệu điều này và cho thấy rằng 50 yêu cầu đồng thời đầu tiên đã 50 * 1s nhưng với hành vi không đồng bộ tại chỗ chỉ có 1,2 s trong tổng số.

Nhưng sau khi thấy điều này tôi vẫn có một số câu hỏi.

  1. Tại sao chúng ta không thể sử dụng một luồng lớn hơn? Không sử dụng async/await để đưa lên một thread chậm hơn sau đó chỉ cần tăng threadpool từ đầu? Nó không giống như các máy chủ chúng tôi chạy trên đột nhiên nhận được nhiều chủ đề hoặc một cái gì đó?

  2. Yêu cầu từ người dùng vẫn đang chờ chuỗi không đồng bộ kết thúc. Nếu chuỗi từ hồ bơi đang làm điều gì đó khác, thì chủ đề 'Giao diện người dùng' vẫn bận như thế nào? Steve đã đề cập đến một cái gì đó về 'một hạt nhân thông minh mà biết khi nào một cái gì đó kết thúc'. Cái này hoạt động ra sao?

Trả lời

55

Đây là một câu hỏi rất hay và hiểu được đó là chìa khóa để hiểu tại sao IO không đồng bộ lại quan trọng như vậy. Lý do tại sao tính năng async/await mới đã được thêm vào C# 5.0 là đơn giản hóa việc viết mã không đồng bộ. Hỗ trợ xử lý không đồng bộ trên máy chủ không phải là mới tuy nhiên, nó tồn tại kể từ ASP.NET 2.0.

Giống như Steve cho bạn thấy, với xử lý đồng bộ, mỗi yêu cầu trong ASP.NET (và WCF) lấy một chủ đề từ nhóm chủ đề. Vấn đề anh ta giới thiệu là một vấn đề nổi tiếng được gọi là "starvation thread thread". Nếu bạn tạo IO đồng bộ trên máy chủ của mình, luồng thread thread sẽ vẫn bị chặn (không làm gì) trong suốt thời gian của IO.Vì có một giới hạn về số lượng các chủ đề trong nhóm luồng, dưới tải, điều này có thể dẫn đến tình huống mà tất cả các luồng chủ đề đang bị chặn chờ IO và các yêu cầu bắt đầu được xếp hàng đợi, làm tăng thời gian phản hồi. Vì tất cả các chủ đề đang chờ một IO hoàn thành, bạn sẽ thấy một CPU chiếm gần 0% (mặc dù thời gian đáp ứng đi qua mái nhà).

Những gì bạn đang yêu cầu (Tại sao chúng ta không thể sử dụng một threadpool lớn hơn?) là một câu hỏi rất hay. Như một vấn đề của thực tế, đây là cách hầu hết mọi người đã được giải quyết vấn đề đói hồ bơi thread cho đến bây giờ: chỉ có nhiều chủ đề trên hồ bơi thread. Một số tài liệu từ Microsoft thậm chí chỉ ra rằng như là một sửa chữa cho các tình huống khi hồ bơi thread đói có thể xảy ra. Đây là một giải pháp có thể chấp nhận được, và cho đến khi C# 5.0, nó dễ dàng hơn nhiều để làm điều đó, hơn viết lại mã của bạn hoàn toàn không đồng bộ.

Có một vài vấn đề với phương pháp này mặc dù:

  • Không có giá trị mà làm việc trong mọi tình huống: số lượng đề hồ bơi thread bạn sẽ cần phụ thuộc tuyến tính vào thời gian IO và tải trên máy chủ của bạn. Thật không may, độ trễ IO hầu như không thể đoán trước. Đây là một ví dụ: Giả sử bạn thực hiện yêu cầu HTTP cho dịch vụ web của bên thứ ba trong ứng dụng ASP.NET của bạn, mất khoảng 2 giây để hoàn tất. Bạn gặp phải nạn đói trong hồ bơi chủ đề, vì vậy bạn quyết định tăng kích thước nhóm luồng lên, giả sử, 200 chủ đề, và sau đó nó bắt đầu hoạt động tốt trở lại. Vấn đề là có thể vào tuần tới, dịch vụ web sẽ có vấn đề kỹ thuật làm tăng thời gian phản ứng của họ lên 10 giây. Tất cả sự đột ngột của hồ bơi, chủ đề đói là trở lại, bởi vì chủ đề bị chặn lâu hơn 5 lần, vì vậy bây giờ bạn cần phải tăng số lượng 5 lần, lên 1000 chủ đề.

  • Khả năng mở rộng và hiệu suất: Vấn đề thứ hai là nếu bạn làm như vậy, bạn sẽ vẫn sử dụng một chuỗi cho mỗi yêu cầu. Chủ đề là một nguồn tài nguyên đắt tiền. Mỗi luồng được quản lý trong .NET yêu cầu cấp phát bộ nhớ 1 MB cho ngăn xếp. Đối với một trang web tạo IO trong 5 giây, và với tải 500 yêu cầu mỗi giây, bạn sẽ cần 2,500 luồng trong nhóm chủ đề của mình, điều đó có nghĩa là 2,5 GB bộ nhớ cho các chuỗi các chuỗi sẽ không làm gì cả. Sau đó, bạn có vấn đề về chuyển đổi ngữ cảnh, điều này sẽ làm mất hiệu suất hoạt động của máy của bạn (ảnh hưởng đến tất cả các dịch vụ trên máy tính, không chỉ ứng dụng web của bạn). Mặc dù Windows thực hiện khá tốt công việc bỏ qua các chủ đề chờ đợi, nhưng nó không được thiết kế để xử lý một số lượng lớn các luồng. Hãy nhớ rằng hiệu quả cao nhất thu được khi số lượng các luồng chạy bằng với số lượng các CPU logic trên máy (thường không quá 16).

Vì vậy, tăng kích thước của nhóm luồng là giải pháp và mọi người đã làm điều đó trong một thập kỷ (ngay cả trong các sản phẩm của Microsoft), nó chỉ ít khả năng mở rộng và hiệu quả hơn về bộ nhớ và CPU sử dụng, và bạn luôn luôn ở lòng thương xót của một sự gia tăng đột ngột của IO độ trễ mà có thể gây ra đói. Cho đến C# 5.0, độ phức tạp của mã không đồng bộ không đáng để gây rắc rối cho nhiều người. async/await thay đổi mọi thứ như bây giờ, bạn có thể hưởng lợi từ khả năng mở rộng của IO không đồng bộ, và viết mã đơn giản, cùng một lúc.

Chi tiết khác: http://msdn.microsoft.com/en-us/library/ff647787.aspx "Sử dụng cuộc gọi không đồng bộ để gọi dịch vụ web hoặc đối tượng ở xa khi có cơ hội thực hiện xử lý song song bổ sung trong khi cuộc gọi dịch vụ Web tiến hành. Các cuộc gọi dịch vụ Web đi được thực hiện bằng cách sử dụng các chủ đề từ nhóm chủ đề ASP.NET Chặn các cuộc gọi để giảm số lượng các chuỗi có sẵn để xử lý các yêu cầu gửi đến khác. "

+14

Trả lời này không trả lời phần thứ hai của câu hỏi. – nunespascal

+1

Tốt hợp lý về lý do tại sao chuyển sang mẫu không đồng bộ. – eduncan911

+3

Tôi không nghĩ rằng điều này giải quyết một thực tế rằng bất kể I/O là không thể đoán trước và bất cứ điều gì khác được quy định, người dùng vẫn phải chờ đợi cho tất cả mọi thứ để có được thực hiện trước khi nhận được một phản ứng. Thực tế là bản thân máy chủ web/http có thể xử lý tải nhiều hơn không có nghĩa là nó có thể xử lý hoàn toàn yêu cầu. Tôi không thấy làm thế nào async giải quyết điều này khác hơn là thay đổi cách thức mọi thứ được phân phối và có khả năng giới thiệu chuyển đổi bối cảnh đắt tiền hơn. – nilskp

29
  1. Async/chờ đợi không dựa trên chủ đề; nó được dựa trên xử lý không đồng bộ. Khi bạn thực hiện một sự chờ đợi không đồng bộ trong ASP.NET, chuỗi yêu cầu được trả lại cho nhóm luồng, vì vậy có các chủ đề no phục vụ yêu cầu đó cho đến khi hoạt động async hoàn tất. Vì yêu cầu overhead thấp hơn so với thread overhead, điều này có nghĩa là async/await có thể mở rộng tốt hơn so với thread pool.
  2. Yêu cầu có số hoạt động không đồng bộ xuất sắc. Số lượng này được quản lý bởi việc triển khai ASP.NET SynchronizationContext. Bạn có thể đọc thêm về SynchronizationContext trong my MSDN article - nó bao gồm cách hoạt động của SynchronizationContext của ASP.NET và cách await sử dụng SynchronizationContext.

ASP.NET không đồng bộ xử lý là tốt trước khi async/chờ đợi - bạn có thể sử dụng các trang async, và sử dụng các thành phần EAP như WebClient (tổ chức sự kiện dựa trên trình không đồng bộ là một phong cách lập trình không đồng bộ dựa trên SynchronizationContext). Đồng bộ/chờ đợi cũng sử dụng SynchronizationContext, nhưng có cú pháp dễ dàng hơn nhiều.

+1

Vẫn còn một chút khó khăn để tôi hiểu nhưng cảm ơn thông tin và bài viết của bạn. Nó làm rõ mọi thứ một chút :) Bạn có thể giải thích sự khác biệt lớn giữa việc xử lý và chủ đề không đồng bộ không? Tôi nghĩ rằng nếu tôi thực hiện một số mã với await rằng nó sẽ chạy trên một thread khác nhau để thread hiện tại có thể trở lại hồ bơi. –

+2

@WouterdeKort the 'async' làm cho mã chạy không đồng bộ nhưng không bắt đầu một chuỗi mới, giống như nó đang thực thi mã trong luồng hiện tại nhưng 'SynchronizationContext' sẽ hoán đổi giữa dòng mã async và phần còn lại của method ... –

+1

@Wouter Xử lý không đồng bộ không yêu cầu luồng. Trong ASP.NET, nếu bạn đang chờ đợi một thao tác chưa hoàn thành, thì 'await' sẽ lên lịch phần còn lại của phương thức như là một sự tiếp tục và trả về. Các chủ đề được trả về hồ bơi thread, không để lại chủ đề phục vụ yêu cầu. Sau đó, khi hoạt động 'await' hoàn thành, nó sẽ lấy một luồng từ nhóm luồng và tiếp tục phục vụ yêu cầu trên luồng đó. Vì vậy, lập trình không đồng bộ không phụ thuộc vào chủ đề. Mặc dù nó hoạt động tốt với các chủ đề nếu bạn cần nó: bạn có thể 'chờ đợi 'một thao tác pool thread bằng cách sử dụng' Task.Run'. –

6

Imagine threadpool là một tập hợp người lao động mà bạn đã sử dụng để làm công việc của bạn. Nhân của bạn chạy nhanh cpu hướng dẫn bạn mã.

Bây giờ công việc của bạn xảy ra phụ thuộc vào công việc của một người chậm chạp khác, anh chàng chậm chạp là đĩa hoặc mạng .Ví dụ, công việc của bạn có thể có hai phần, một phần phải thực hiện trước công việc của người chậm chạp, và một phần phải xecute sau công việc của người chậm chạp.

Bạn sẽ khuyên người lao động làm công việc của bạn như thế nào? Bạn có nói với mỗi nhân viên - "Làm phần đầu tiên này, sau đó đợi cho đến khi anh chàng chậm chạp đó làm xong, và sau đó làm phần thứ hai của bạn"? Bạn sẽ tăng số lượng công nhân của bạn bởi vì tất cả họ dường như đang chờ đợi anh chàng chậm chạp đó và bạn không thể làm hài lòng khách hàng mới? Không!

Thay vào đó, bạn sẽ yêu cầu mỗi nhân viên làm phần đầu tiên và yêu cầu người chậm lại quay lại và thả một tin nhắn vào hàng đợi khi hoàn thành. Bạn sẽ nói với từng nhân viên (hoặc có thể là một tập hợp con chuyên dụng của công nhân) để tìm kiếm các thông báo được thực hiện trong hàng đợi và thực hiện phần thứ hai của tác phẩm.

hạt nhân thông minh bạn đang ám chỉ ở trên là khả năng của hệ điều hành để duy trì hàng đợi như vậy đối với các thông báo hoàn thành IO và đĩa chậm.

+2

Giải thích tuyệt vời, thực sự đóng đinh nó cho tôi, cảm ơn! –

Các vấn đề liên quan