簡體   English   中英

如何使用多個線程處理大型文本文件中的內容?

[英]How to process contents from large text file using multiple threads?

我必須閱讀一個包含文本的巨大文件,大約3GB(和4000萬行)。 只是閱讀它的過程非常快:

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
  while ((line = br.readLine()) != null) {
    //Nothing here
  }
}

從上面的代碼中讀取每一line ,我都會對字符串進行一些解析,然后進一步處理它(一項艱巨的任務)。 我嘗試做多個線程

A)我已經像這樣嘗試過BlockingQueue

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
            String line;
            BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
            int numThreads = 5;
            Consumer[] consumer = new Consumer[numThreads];
            for (int i = 0; i < consumer.length; i++) {
                consumer[i] = new Consumer(queue);
                consumer[i].start();
            }
            while ((line = br.readLine()) != null) {
                queue.put(line);
            }
            queue.put("exit");
        } catch (FileNotFoundException ex) {
            Logger.getLogger(ReadFileTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException | InterruptedException ex) {
            Logger.getLogger(ReadFileTest.class.getName()).log(Level.SEVERE, null, ex);
        }

class Consumer extends Thread {
        private final BlockingQueue<String> queue;
        Consumer(BlockingQueue q) {
            queue = q;
        }
        public void run() {
            while (true) {
                try {
                    String result = queue.take();
                    if (result.equals("exit")) {
                        queue.put("exit");
                        break;
                    }
                    System.out.println(result);
                } catch (InterruptedException ex) {
                    Logger.getLogger(ReadFileTest.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

        }
    }

這種方法比普通的單線程處理花費更多的時間。 我不確定為什么-我做錯了什么?

B)我嘗試了ExecutorService

 try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
            String line; 
            ExecutorService pool = Executors.newFixedThreadPool(10);         
             while ((line = br.readLine()) != null) {
                 pool.execute(getRunnable(line));
            }
             pool.shutdown();
         } catch (FileNotFoundException ex) {
            Logger.getLogger(ReadFileTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ReadFileTest.class.getName()).log(Level.SEVERE, null, ex);
        }

  private static Runnable getRunnable(String run){
        Runnable task = () -> {
            System.out.println(run);
        };
        return task;
    }

與直接在while循環內打印相比,此方法還需要更多時間。 我究竟做錯了什么?

正確的方法是什么?

如何有效地處理具有多個線程的讀取line

在這里回答一部分-為什么BlockingQueue選項變慢。

重要的是要了解線程不是免費的。 起來並“管理”它們總是需要一定的開銷

當然,當您實際使用的線程數超過硬件“本地”支持的線程數時, 上下文切換就會添加到帳單中。

除此之外, BlockingQueue也不免費。 您會看到,為了保持 順序 ,ArrayBlockingQueue可能必須在某個地方進行同步 最壞的情況是意味着鎖定並等待。 是的,JVM和JIT通常在這些方面都相當不錯。 但是同樣,一定的“百分比”會添加到帳單中。

但是,僅作記錄,沒關系。 javadoc

此類支持可選的公平性策略,用於訂購正在等待的生產者和使用者線程。 默認情況下,不保證此排序。 但是,將公平性設置為true構造的隊列將按FIFO順序授予線程訪問權限。 公平通常會降低吞吐量,但會減少可變性並避免飢餓。

由於您未設置“公平”

BlockingQueue隊列=新的ArrayBlockingQueue <>(100);

那不應該影響你。 另一方面:我很確定您希望這些行會按順序處理,因此您實際上想繼續

BlockingQueue<String> queue = new ArrayBlockingQueue<>(100, true);

從而進一步減慢了整個過程。

最后:我同意到目前為止的評論。 對這些事情進行基准測試是一項復雜的工作; 許多方面都會影響結果。 最重要的問題肯定是:您的瓶頸在哪里? 是IO性能(然后更多的線程幫不了多大忙!)-還是真的是整體處理時間(然后用於處理的“正確”線程數肯定會加快處理速度)。

關於“如何以正確的方式執行此操作”,我建議在softwareengineering.SE上查看此問題

如何使用多個線程處理大型文本文件中的內容?

如果您的計算機有足夠的RAM ,我將執行以下操作:

  • 將整個文件讀取到一個變量中(例如,一個ArrayList)-僅使用一個線程讀取整個文件。

  • 然后啟動一個ExecutorService(線程池使用的線程數不超過您計算機可以同時運行的最大內核數)

      int cores = Runtime.getRuntime().availableProcessors(); ExecutorService executorService = Executors.newFixedThreadPool(cores); 
  • 最后,將讀取的行划分為有限數量的可調用項/可運行項,然后將這些可調用項/可運行項提交給您的ExecutorService(以便它們都可以在您的ExecutorService中同時執行)。

除非您的行處理使用I / O,否則我假定您將達到接近100%的CPU利用率,並且您的所有線程都不會處於等待狀態。

您想要更快的處理速度嗎?

垂直擴展是最簡單的選擇:購買更多的RAM,更好的CPU(具有更多內核),使用固態驅動器

可能是所有線程同時訪問同一共享資源,因此導致更多爭議。 您可以嘗試使用讀取器線程將所有行放在單個鍵中的方法以分區方式提交,這樣一來,它的爭用就會減少。

公共無效執行(Runnable命令){

    final int key= command.getKey();
     //Some code to check if it is runing
    final int index = key != Integer.MIN_VALUE ? Math.abs(key) % size : 0;
    workers[index].execute(command);
}

創建帶有隊列的工作程序,以便如果需要順序執行某些任務,則運行它。

private final AtomicBoolean scheduled = new AtomicBoolean(false);

private final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(maximumQueueSize);

public void execute(Runnable command) {
    long timeout = 0;
    TimeUnit timeUnit = TimeUnit.SECONDS;
    if (command instanceof TimeoutRunnable) {
        TimeoutRunnable timeoutRunnable = ((TimeoutRunnable) command);
        timeout = timeoutRunnable.getTimeout();
        timeUnit = timeoutRunnable.getTimeUnit();
    }

    boolean offered;
    try {
        if (timeout == 0) {
            offered = workQueue.offer(command);
        } else {
            offered = workQueue.offer(command, timeout, timeUnit);
        }
    } catch (InterruptedException e) {
        throw new RejectedExecutionException("Thread is interrupted while offering work");
    }

    if (!offered) {
        throw new RejectedExecutionException("Worker queue is full!");
    }

    schedule();
}

private void schedule() {
    //if it is already scheduled, we don't need to schedule it again.
    if (scheduled.get()) {
        return;
    }

    if (!workQueue.isEmpty() && scheduled.compareAndSet(false, true)) {
        try {
            executor.execute(this);
        } catch (RejectedExecutionException e) {
            scheduled.set(false);
            throw e;
        }
    }
}

public void run() {
    try {
        Runnable r;
        do {
            r = workQueue.poll();
            if (r != null) {
                r.run();
            }
        }
        while (r != null);
    } finally {
        scheduled.set(false);
        schedule();
    }
}

如上所述,線程池大小沒有固定的規則,但是根據您的用例,有一些建議或最佳實踐可以使用。

CPU綁定任務

For CPU bound tasks, Goetz (2002, 2006) recommends

threads = number of CPUs + 1

IO綁定任務

Working out the optimal number for IO bound tasks is less obvious. During an IO bound task, a CPU will be left idle (waiting or blocking). This idle time can be better used in initiating another IO bound request.

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM