Java: Framwork Fork/Join

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Framework Fork/Join là một sự thực thi của interface ExecutorService giúp bạn tận dụng ưu điểm của bộ xử lý đa nhân. Nó được thiết kế cho công việc mà có thể được chia thành các phần nhỏ hơn mang tính đệ quy. Mục đích là để sử dụng tất cả các khả năng xử lý để nâng cao hiệu suất cho các ứng dụng của bạn.

Với bất kỳ sự thực thi ExecutorService nào thì framework fork/join sẽ phân chia các tác vụ cho các luồng công tác trong thread pool. Framework fork/join có tính riêng biệt, bởi vì nó sử dụng thuật toán work-stealing. Các luồng công tác sẽ ra khỏi những điều để làm nhiệm vụ "lấy bớt" các tác vụ từ các luồng khác đang bận rộn.

Trung tâm của framework fork/join là lớp ForkJoinPool, đây là phần mở rộng của lớp AbstractExecutorService. ForkJoinPool thực hiện thuật toán work-stealing cốt lõi và có thể thực hiện tiến trình ForkJoinTask.

Cách sử dụng cơ bản

Bước đầu tiên để sử dụng framework fork/join là viết mã thực hiện một phân đoạn của công việc. Mã của bạn sẽ trông giống như đoạn giả mã sau đây:

if (my portion of the work is small enough)
  do the work directly
else
  split my work into two pieces
  invoke the two pieces and wait for the results

Gộp đoạn mã này vào lớp con ForkJoinTask bằng cách sử dụng một trong các kiểu đặc trưng của nó là RecursiveTask (có thể trả về một kết quả) hoặc RecursiveAction.

Sau khi lớp con ForkJoinTask của bạn sẵn sàng thì cần tạo đối tượng đại diện cho tất cả các công việc phải thực hiện và truyền nó tới phương thức invoke() của thể hiện ForkJoinPool.

Làm mờ tính rõ ràng

Để hiểu được các công việc của framework fork/join, ta sẽ xét ví dụ dưới đây. Giả sử rằng bạn muốn làm mờ hình ảnh. Ảnh gốc được biểu diễn bằng một mảng các số nguyên, trong đó mỗi số nguyên chứa các giá trị màu cho một điểm ảnh duy nhất. Ảnh đích được làm mờ cũng được đại diện bởi một dãy số nguyên có kích thước tương tự như ảnh nguồn.

Việc làm mờ được thực hiện bằng cách tại một thời điểm ta làm việc với một điểm ảnh của mảng nguồn. Mỗi điểm ảnh được lấy trung bình với các điểm ảnh xung quanh nó (các thành phần màu đỏ, xanh lá cây, và màu xanh blue được tính trung bình), và kết quả được đặt trong mảng đích. Vì hình ảnh là một mảng lớn, nên quá trình này có thể mất một thời gian dài. Bạn có thể tận dụng lợi thế của xử lý đồng thời trên hệ thống đa nhân bằng cách thực hiện thuật toán framework fork/join. Dưới đây là ví dụ cụ thể:

public class ForkBlur extends RecursiveAction {
    private int[] mSource;
    private int mStart;
    private int mLength;
    private int[] mDestination;

    // Processing window size; should be odd.
    private int mBlurWidth = 15;

    public ForkBlur(int[] src, int start, int length, int[] dst) {
        mSource = src;
        mStart = start;
        mLength = length;
        mDestination = dst;
    }

    protected void computeDirectly() {
        int sidePixels = (mBlurWidth - 1) / 2;
        for (int index = mStart; index < mStart + mLength; index++) {
            // Calculate average.
            float rt = 0, gt = 0, bt = 0;
            for (int mi = -sidePixels; mi <= sidePixels; mi++) {
                int mindex = Math.min(Math.max(mi + index, 0),
                                    mSource.length - 1);
                int pixel = mSource[mindex];
                rt += (float)((pixel & 0x00ff0000) >> 16)
                      / mBlurWidth;
                gt += (float)((pixel & 0x0000ff00) >>  8)
                      / mBlurWidth;
                bt += (float)((pixel & 0x000000ff) >>  0)
                      / mBlurWidth;
            }

            // Reassemble destination pixel.
            int dpixel = (0xff000000     ) |
                   (((int)rt) << 16) |
                   (((int)gt) <<  8) |
                   (((int)bt) <<  0);
            mDestination[index] = dpixel;
        }
    }

  ...

Bây giờ ta thực hiện phương thức trừu tượng compute(), hoặc làm mờ trực tiếp hoặc tách nó thành hai tác vụ nhỏ hơn. Ngưỡng kích thước của mảng sẽ giúp xác định xem công việc được thực hiện trực tiếp hay chia tách.

protected static int sThreshold = 100000;

protected void compute() {
    if (mLength < sThreshold) {
        computeDirectly();
        return;
    }

    int split = mLength / 2;

    invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
              new ForkBlur(mSource, mStart + split, mLength - split,
                           mDestination));
}

Nếu các phương thức trước đó đang ở trong một lớp con của lớp RecursiveAction, thì thiết lập tác vụ để chạy trong một ForkJoinPool là đơn giản, và bao gồm các bước sau đây:​

  1. Tạo một tác vụ để thể hiện tất cả các công việc được thực hiện.

    // source image pixels are in src
    // destination image pixels are in dst
    ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
    
  2. Tạo ForkJoinPool để thực hiện tác vụ.

    ForkJoinPool pool = new ForkJoinPool();
    
  3. Thực hiện tác vụ.

    pool.invoke(fb);

Chuẩn thực thi

Ngoài việc sử dụng framework fork/join để thực hiện thuật toán tùy chỉnh cho các nhiệm vụ phải được thực hiện đồng thời trên một hệ thống đa nhân (như ví dụ ForkBlur.java trong phần ), còn có một số tính năng hữu ích trong Java SE mà đã được thực hiện bằng cách sử dụng framework fork/join. Một ví dụ thực hiện, được giới thiệu trong Java SE 8, được sử dụng bởi lớp java.util.Arrays  với các phương thức parallelSort() của nó. Những phương thức này tương tự như các phương thức sort(), nhưng thực hiện tính đồng thời thông qua framework fork/join. Việc sắp xếp song song mảng lớn sẽ nhanh hơn so với việc sắp xếp tuần tự khi chạy trên các hệ thống đa xử lý. Tuy nhiên, làm thế nào chính xác hóa framework fork/join được thừa hưởng bởi các phương thức này thì lại nằm ngoài phạm vi của các bài hướng dẫn này. Để biết thêm về điều này, hãy xem tài liệu Java API.

Một sự thực thi framework fork/join khác được sử dụng bởi các phương thức trong gói java.util.streams, đây là một phần của Dự án Lambda đã được phát hành  cho Java SE 8. Để biết thêm thông tin, xin xem phần Biểu thức Lambda.

» Tiếp: Gói concurrent
« Trước: Thread Pool
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!