2011-05-16 34 views
8

Paul Tyma presentation có dòng này:Tại sao một ExecutorService được tạo thông qua newCachedThreadPool evil?

Executors.newCacheThreadPool ác, chết chết chết

Tại sao nó ác?

Tôi sẽ gây nguy hiểm khi đoán: có phải vì số lượng chủ đề sẽ phát triển theo kiểu không giới hạn. Vì vậy, một máy chủ đã được slashdotted, có lẽ sẽ chết nếu số chủ đề tối đa của JVM đã đạt được?

+1

Tôi đã sử dụng một nhóm bộ nhớ cache treo hệ thống Windows Vista của mình quá tệ đến nỗi tôi phải cấp nguồn cho nó. Trình quản lý tác vụ sẽ không phản hồi. –

+0

đến đây vì lý do tương tự :) – Eugen

Trả lời

8

Sự cố với Executors.newCacheThreadPool() là người thực thi sẽ tạo và bắt đầu nhiều chuỗi khi cần thiết để thực hiện các tác vụ được gửi đến nó. Trong khi điều này được giảm thiểu bởi thực tế là các luồng hoàn thành được giải phóng (các ngưỡng có thể cấu hình được), điều này thực sự có thể dẫn đến việc đói tài nguyên nghiêm trọng hoặc thậm chí làm hỏng JVM (hoặc một số hệ điều hành được thiết kế xấu).

4

Có một số vấn đề với nó. Unbounded tăng trưởng về chủ đề một vấn đề rõ ràng - nếu bạn có nhiệm vụ ràng buộc cpu thì cho phép nhiều hơn CPU có sẵn để chạy chúng chỉ đơn giản là tạo ra trên không lịch trình với ngữ cảnh chủ đề của bạn chuyển đổi khắp nơi và không thực sự tiến triển nhiều. Nếu nhiệm vụ của bạn là IO bị ràng buộc mặc dù mọi thứ trở nên tinh tế hơn. Biết cách kích thước các luồng của các luồng đang chờ trên mạng hoặc tệp IO là khó khăn hơn nhiều và phụ thuộc rất nhiều vào độ trễ của các sự kiện IO đó. Độ trễ cao hơn có nghĩa là bạn cần (và có thể hỗ trợ) nhiều chủ đề hơn.

Nhóm chủ đề được lưu trong bộ nhớ cache tiếp tục thêm chuỗi mới vì tốc độ tạo tác vụ vượt quá tốc độ thực thi. Có một vài rào cản nhỏ đối với điều này (chẳng hạn như các khóa nối tiếp tạo chuỗi id mới) nhưng điều này có thể tăng trưởng không bị ràng buộc có thể dẫn đến lỗi ngoài bộ nhớ.

Một vấn đề lớn khác với nhóm luồng được lưu trong bộ nhớ cache là nó có thể chậm đối với luồng sản xuất tác vụ. Các hồ bơi được cấu hình với một SynchronousQueue cho các nhiệm vụ được cung cấp. Việc triển khai hàng đợi này về cơ bản có kích thước bằng 0 và chỉ hoạt động khi có một người tiêu dùng phù hợp với một nhà sản xuất (có một cuộc thăm dò chủ đề khi người khác đang cung cấp). Việc thực hiện thực tế đã được cải thiện đáng kể trong Java6, nhưng nó vẫn tương đối chậm đối với nhà sản xuất, đặc biệt khi nó không thành công (khi nhà sản xuất chịu trách nhiệm tạo ra một luồng mới để thêm vào hồ bơi). Thông thường nó là lý tưởng hơn cho các nhà sản xuất sợi chỉ đơn giản là thả nhiệm vụ trên một hàng đợi thực tế và tiếp tục.

Vấn đề là, không ai có một nhóm có một tập hợp chủ đề nhỏ mà khi tất cả chúng bận rộn tạo ra các chủ đề mới lên đến một số tối đa và sau đó enqueues nhiệm vụ tiếp theo. Cố định hồ bơi dường như hứa hẹn này, nhưng họ chỉ bắt đầu thêm nhiều chủ đề khi hàng đợi cơ bản từ chối nhiều nhiệm vụ hơn (nó là đầy đủ). A LinkedBlockingQueue không bao giờ được đầy đủ để các hồ bơi không bao giờ phát triển vượt ra ngoài kích thước lõi. An ArrayBlockingQueue có công suất, nhưng vì nó chỉ phát triển các hồ bơi khi công suất đạt được điều này không làm giảm tốc độ sản xuất cho đến khi nó đã là một vấn đề lớn. Hiện tại, giải pháp yêu cầu sử dụng một số rejected execution policy tốt như người gọi chạy, nhưng nó cần được chăm sóc.

Nhà phát triển thấy hồ bơi chuỗi được lưu trong bộ nhớ cache và sử dụng một cách mù quáng mà không thực sự nghĩ đến hậu quả.

16

(Đây là Paul)

Mục đích của slide là (ngoài có từ ngữ khôi hài), như bạn đề cập đến, đó là hồ bơi thread phát triển mà không bị ràng buộc tạo chủ đề mới.

Một nhóm chủ đề vốn đại diện cho hàng đợi và điểm chuyển giao công việc trong một hệ thống. Đó là, một cái gì đó là cho ăn nó làm việc để làm (và nó có thể được cho ăn làm việc ở nơi khác quá). Nếu một hồ bơi thread bắt đầu phát triển bởi vì nó không thể theo kịp nhu cầu.

Nói chung, điều đó là tốt vì tài nguyên máy tính là hữu hạn và hàng đợi đó được tạo để xử lý các cụm công việc. Tuy nhiên, nhóm chủ đề đó không cho phép bạn kiểm soát việc có thể đẩy nút cổ chai về phía trước.

Ví dụ, trong một kịch bản máy chủ, một vài luồng có thể chấp nhận trên ổ cắm và bàn giao một nhóm luồng khách hàng để xử lý. Nếu hồ bơi thread đó bắt đầu phát triển ngoài tầm kiểm soát - hệ thống sẽ dừng chấp nhận các máy khách mới (trên thực tế, các chủ đề "acceptor" sau đó thường nhảy vào nhóm luồng tạm thời để giúp xử lý các máy khách).

Hiệu ứng tương tự nếu bạn sử dụng một hồ bơi cố định với hàng đợi đầu vào không bị chặn. Bất cứ lúc nào bạn xem xét các kịch bản của hàng đợi điền vào kiểm soát - bạn nhận ra vấn đề.

IIRC, máy chủ SEDA seminal của Matt Welsh (không đồng bộ) đã tạo các nhóm luồng đã sửa đổi kích thước của chúng theo đặc điểm của máy chủ.

Ý tưởng dừng chấp nhận khách hàng mới có vẻ xấu cho đến khi bạn nhận ra sự thay thế là một hệ thống bị tê liệt không xử lý khách hàng. (Một lần nữa, với sự hiểu biết rằng máy tính là hữu hạn - ngay cả một hệ thống được điều chỉnh tối ưu có giới hạn)

Ngẫu nhiên, các JVM giới hạn chủ đề đến 16k (thường) hoặc 32k tùy thuộc vào JVM. Nhưng nếu bạn là CPU bị ràng buộc, giới hạn đó không phải là rất có liên quan - bắt đầu từ một thread khác trên một hệ thống CPU ràng buộc là phản tác dụng.

Tôi đã vui vẻ chạy hệ thống ở 4 hoặc 5 nghìn luồng. Nhưng gần 16k giới hạn những thứ có xu hướng bog xuống (JVM giới hạn này thực thi - chúng tôi đã có nhiều chủ đề hơn trong Linux C + +) ngay cả khi không bị ràng buộc CPU.

+0

Vì đây là tác giả của báo giá gốc và anh ấy đã cung cấp phản hồi toàn diện nhất, tôi cảm thấy điều này nên là câu trả lời được chấp nhận. – KomodoDave

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