Tại sao cần shutdown of forked jvm

Tôi đang viết một chương trình Java sử dụng rất nhiều CPU vì bản chất của những gì nó làm. Tuy nhiên, rất nhiều trong số đó có thể chạy song song và tôi đã làm cho chương trình của mình đa luồng Khi tôi chạy nó, dường như chỉ sử dụng một CPU cho đến khi nó cần nhiều hơn thì nó sử dụng CPU khác - có bất cứ điều gì tôi có thể làm trong Java để buộc các luồng khác nhau chạy trên các lõi khác nhau/CPU?

Khi tôi chạy nó, dường như chỉ sử dụng một CPU cho đến khi nó cần nhiều hơn thì nó sử dụng CPU khác - có bất cứ điều gì tôi có thể làm trong Java để buộc các luồng khác nhau chạy trên các lõi/CPU khác nhau ?

Tôi giải thích phần này của câu hỏi của bạn là có nghĩa là bạn đã giải quyết vấn đề làm cho ứng dụng của bạn có khả năng đa luồng. Và mặc dù vậy, nó không ngay lập tức bắt đầu sử dụng nhiều lõi.

Câu trả lời cho "có cách nào để ép buộc ..." là (AFAIK) không trực tiếp. JVM của bạn và/hoặc Hệ điều hành máy chủ quyết định sẽ sử dụng bao nhiêu luồng 'gốc' và cách các luồng đó được ánh xạ tới các bộ xử lý vật lý. Bạn có một số tùy chọn để điều chỉnh. Ví dụ: tôi đã tìm thấy trang này trong đó nói về cách điều chỉnh Java luồng trên Solaris. Và trang này nói về những thứ khác có thể làm chậm một ứng dụng đa luồng.

The most convenient and reliable file storage service

Receive your personal cloud storage with 2Gb of space for free

Có hai cách cơ bản để đa luồng trong Java. Mỗi tác vụ logic bạn tạo bằng các phương thức này sẽ chạy trên lõi mới khi cần và có sẵn.

Phương thức một: xác định đối tượng Runnable hoặc Thread (có thể lấy Runnable trong hàm tạo) và bắt đầu chạy với phương thức Thread.start (). Nó sẽ thực thi trên bất kỳ lõi nào mà HĐH cung cấp cho nó - nói chung là loại ít tải hơn.

Hướng dẫn: Xác định và bắt đầu chủ đề

Phương pháp hai: xác định các đối tượng triển khai giao diện Runnable (nếu chúng không trả về giá trị) hoặc Callable (nếu có), chứa mã xử lý của bạn. Truyền các nhiệm vụ này cho ExecutorService từ gói Java.util.conc hiện. Lớp Java.util.concản.Executors có một loạt các phương thức để tạo ra các loại ExecutorService tiêu chuẩn, hữu ích. Liên kết để hướng dẫn thực thi.

Từ kinh nghiệm cá nhân, các nhóm luồng cố định và bộ nhớ cache của Executors rất tốt, mặc dù bạn sẽ muốn Tweak đếm số luồng. Runtime.getR.78 (). AvailableProcessors () có thể được sử dụng vào thời gian chạy để đếm các lõi có sẵn. Bạn sẽ cần tắt các nhóm luồng khi ứng dụng của bạn hoàn tất, nếu không ứng dụng sẽ không thoát vì các luồng ThreadPool vẫn chạy.

Để có được hiệu suất đa lõi tốt đôi khi rất khó và có nhiều vấn đề:

  • Đĩa I/O làm chậm RẤT NHIỀU khi chạy song song. Mỗi lần chỉ có một luồng nên đọc/ghi đĩa.
  • Đồng bộ hóa các đối tượng cung cấp sự an toàn cho các hoạt động đa luồng, nhưng làm chậm công việc.
  • Nếu các tác vụ quá tầm thường (các bit công việc nhỏ, thực thi nhanh) thì chi phí quản lý chúng trong ExecutorService sẽ tốn nhiều chi phí hơn bạn có được từ nhiều lõi.
  • Tạo các đối tượng Thread mới là chậm. ExecutorService sẽ cố gắng sử dụng lại các luồng hiện có nếu có thể.
  • Tất cả các loại công cụ điên rồ có thể xảy ra khi nhiều chủ đề làm việc trên một cái gì đó. Giữ cho hệ thống của bạn đơn giản và cố gắng làm cho các tác vụ trở nên khác biệt và không tương tác.

Một vấn đề khác: kiểm soát công việc là khó khăn! Một thực hành tốt là có một luồng trình quản lý tạo và gửi các tác vụ, sau đó một vài luồng làm việc với hàng đợi công việc (sử dụng ExecutorService).

Tôi chỉ chạm vào những điểm chính ở đây - lập trình đa luồng được coi là một trong những môn học lập trình khó nhất bởi nhiều chuyên gia. Nó không trực quan, phức tạp và trừu tượng thường yếu.


Chỉnh sửa - Ví dụ sử dụng ExecutorService:

public class TaskThreader {
    class DoStuff implements Callable {
       Object in;
       public Object call(){
         in = doStep1(in);
         in = doStep2(in);
         in = doStep3(in); 
         return in;
       }
       public DoStuff(Object input){
          in = input;
       }
    }

    public abstract Object doStep1(Object input);    
    public abstract Object doStep2(Object input);    
    public abstract Object doStep3(Object input);    

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        ArrayList tasks = new ArrayList();
        for(Object input : inputs){
           tasks.add(new DoStuff(input));
        }
        List results = exec.invokeAll(tasks);
        exec.shutdown();
        for(Future f : results) {
           write(f.get());
        }
    }
}

Trước tiên, bạn nên tự chứng minh rằng chương trình của bạn sẽ chạy nhanh hơn trên nhiều lõi. Nhiều hệ điều hành đặt nỗ lực vào việc chạy các luồng chương trình trên cùng một lõi bất cứ khi nào có thể.

Chạy trên cùng một lõi có nhiều lợi thế. Bộ đệm CPU nóng, có nghĩa là dữ liệu cho chương trình đó được tải vào CPU. Các đối tượng khóa/màn hình/đồng bộ hóa nằm trong bộ đệm CPU, điều đó có nghĩa là các CPU khác không cần thực hiện các hoạt động đồng bộ hóa bộ đệm trên xe buýt (đắt tiền!).

Một điều có thể rất dễ dàng khiến chương trình của bạn chạy trên cùng một CPU mọi lúc là sử dụng quá mức các khóa và bộ nhớ dùng chung. Chủ đề của bạn không nên nói chuyện với nhau. Các chủ đề của bạn càng ít sử dụng cùng một đối tượng trong cùng một bộ nhớ thì chúng sẽ càng chạy trên các CPU khác nhau. Họ càng thường xuyên sử dụng cùng một bộ nhớ, họ càng thường xuyên phải chờ đợi chuỗi khác.

Bất cứ khi nào HĐH nhìn thấy một khối luồng cho luồng khác, nó sẽ chạy luồng đó trên cùng CPU bất cứ khi nào có thể. Nó giảm dung lượng bộ nhớ di chuyển trên bus liên CPU. Đó là những gì tôi đoán là gây ra những gì bạn thấy trong chương trình của bạn.

Đầu tiên, tôi khuyên bạn nên đọc "Đồng thời trong thực tiễn" của Brian Goetz .

Đây là cuốn sách hay nhất mô tả đồng thời Java.

Đồng thời là 'dễ học, khó làm chủ'. Tôi khuyên bạn nên đọc nhiều về chủ đề này trước khi thử nó. Rất dễ dàng để có được một chương trình đa luồng hoạt động chính xác 99,9% thời gian và thất bại 0,1%. Tuy nhiên, đây là một số mẹo để bạn bắt đầu:

Có hai cách phổ biến để làm cho chương trình sử dụng nhiều hơn một lõi:

  1. Làm cho chương trình chạy bằng nhiều quy trình. Một ví dụ là Apache được biên dịch với MPM Pre-Fork, gán các yêu cầu cho các tiến trình con. Trong một chương trình nhiều quá trình, bộ nhớ không được chia sẻ theo mặc định. Tuy nhiên, bạn có thể ánh xạ các phần của bộ nhớ dùng chung trên các quy trình. Apache thực hiện điều này với 'bảng điểm'.
  2. Làm cho chương trình đa luồng. Trong một chương trình đa luồng, tất cả bộ nhớ heap được chia sẻ theo mặc định. Mỗi luồng vẫn có ngăn xếp riêng của nó, nhưng có thể truy cập bất kỳ phần nào của heap. Thông thường, hầu hết Java chương trình là đa luồng và không phải đa tiến trình.

Ở cấp độ thấp nhất, người ta có thể tạo và hủy chủ đề . Java giúp dễ dàng tạo các luồng theo cách đa nền tảng di động.

Vì nó có xu hướng tốn kém để tạo và hủy các luồng mọi lúc, Java hiện bao gồm Executors để tạo nhóm luồng có thể sử dụng lại. Các tác vụ có thể được gán cho người thi hành và kết quả có thể được truy xuất thông qua một đối tượng Tương lai.

Thông thường, một người có một nhiệm vụ có thể được chia thành các nhiệm vụ nhỏ hơn, nhưng kết quả cuối cùng cần phải được đưa lại với nhau. Ví dụ, với một sắp xếp hợp nhất, người ta có thể chia danh sách thành các phần nhỏ hơn và nhỏ hơn, cho đến khi một người có mọi lõi thực hiện việc sắp xếp. Tuy nhiên, khi mỗi danh sách con được sắp xếp, nó cần được hợp nhất để có được danh sách được sắp xếp cuối cùng. Vì đây là vấn đề "phân chia và chinh phục" khá phổ biến, nên có một khung công tác JSR có thể xử lý phân phối và tham gia cơ bản. Khung này có thể sẽ được bao gồm trong Java 7.

Flexible, reliable and affordable cloud hosting

Sign up and get $50 bonus within 30-day!

Bạn nên viết chương trình của mình để thực hiện công việc của nó dưới dạng lot của Callable được trao cho ExecutorService và được thực hiện với invokeAll (...).

Sau đó, bạn có thể chọn một triển khai phù hợp trong thời gian chạy từ lớp Executors. Một gợi ý sẽ là gọi Executors.newFixedThreadPool () với một số gần tương ứng với số lõi cpu để tiếp tục bận.

Bạn có thể sử dụng API bên dưới từ Executors với Java 8

public static ExecutorService newWorkStealingPool()

Tạo nhóm luồng đánh cắp công việc bằng cách sử dụng tất cả các bộ xử lý có sẵn làm mức độ song song đích của nó.

Do cơ chế đánh cắp công việc, các luồng nhàn rỗi ăn cắp các nhiệm vụ từ hàng đợi nhiệm vụ của các luồng bận rộn và thông lượng tổng thể sẽ tăng lên.

Từ grepcode , việc triển khai newWorkStealingPool như sau

/**
     * Creates a work-stealing thread pool using all
     * {@link Runtime#availableProcessors available processors}
     * as its target parallelism level.
     * @return the newly created thread pool
     * @see #newWorkStealingPool(int)
     * @since 1.8
     */
    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

Điều dễ nhất để làm là chia chương trình của bạn thành nhiều quy trình. HĐH sẽ phân bổ chúng trên các lõi.

Khó hơn một chút là chia chương trình của bạn thành nhiều luồng và tin tưởng JVM để phân bổ chúng đúng cách. Đây là - nói chung - những gì mọi người làm để sử dụng phần cứng có sẵn.


Chỉnh sửa

Làm thế nào một chương trình đa xử lý có thể "dễ dàng" hơn? Đây là một bước trong một đường ống dẫn.

public class SomeStep {
    public static void main( String args[] ) {
        BufferedReader stdin= new BufferedReader( System.in );
        BufferedWriter stdout= new BufferedWriter( System.out );
        String line= stdin.readLine();
        while( line != null ) {
             // process line, writing to stdout
             line = stdin.readLine();
        }
    }
}

Mỗi bước trong đường ống có cấu trúc tương tự nhau. 9 dòng trên không cho bất kỳ xử lý được bao gồm.

Đây có thể không phải là hiệu quả tuyệt đối nhất. Nhưng nó rất dễ.


Cấu trúc tổng thể của các quy trình đồng thời của bạn không phải là vấn đề JVM. Đó là một vấn đề hệ điều hành, vì vậy hãy sử dụng Shell.

Java -cp pipline.jar FirstStep | Java -cp pipline.jar SomeStep | Java -cp pipline.jar LastStep

Điều duy nhất còn lại là tìm ra một số tuần tự hóa cho các đối tượng dữ liệu của bạn trong đường ống dẫn. Tiêu chuẩn nối tiếp hoạt động tốt. Đọc http://Java.Sun.com/developer/technicalArticles/Programming/serialization/ để biết gợi ý về cách tuần tự hóa. Bạn có thể thay thế BufferedReaderBufferedWriter bằng ObjectInputStreamObjectOutputStream để thực hiện điều này.

Tôi nghĩ vấn đề này có liên quan đến Java Khung công tác song song (JPPF). Sử dụng điều này bạn có thể chạy các công việc khác nhau trên các bộ xử lý khác nhau.