我有一个程序处理大量文件,每个文件需要完成两件事:首先,读取并处理文件的某些部分,然后存储生成的MyFileData
。第一部分可以并行化,第二部分不能。
按顺序执行所有操作非常慢,因为CPU必须等待磁盘,然后它会工作一段时间,然后它会发出另一个请求,然后再次等待...
我做了以下
class MyCallable implements Callable<MyFileData> {
MyCallable(File file) {
this.file = file;
}
public MyFileData call() {
return someSlowOperation(file);
}
private final File file;
}
for (File f : files) futures.add(executorService.submit(new MyCallable(f)));
for (Future<MyFileData> f : futures) sequentialOperation(f.get());
它帮助了很多。但是,我想改进两件事:
答案 0 :(得分:6)
sequentialOperation以固定顺序执行,而不是先处理可用的结果。我怎样才能改变它?
这正是CompletionService的作用:它并行处理任务并在完成后返回它们,无论提交顺序如何。
简化(未测试)示例:
int NUM_THREADS = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
CompletionService<MyFileData> completionService = new ExecutorCompletionService<MyFileData>(executor);
for (File f : files) futures.add(completionService.submit(new MyCallable(f)));
for(int i = 0; i < futures.size(); i++) {
Future<MyFileData> next = completionService.take();
sequentialOperation(next.get());
}
有数千个文件需要处理,启动数千个磁盘请求可能会导致磁盘丢失。通过使用Executors.newFixedThreadPool(10)我限制了这个数字,但是我正在寻找更好的东西。
我不是100%肯定那个。我想这取决于你有多少磁盘,但我认为磁盘访问部分不应该分成太多的线程(每个磁盘一个线程可能是明智的):如果许多线程同时访问一个磁盘,它将花费更多的时间而不是阅读。
答案 1 :(得分:2)
sequentialOperation以固定顺序执行,而不是先处理可用的结果。我怎样才能改变它?
假设:每次someSlowOperation(file);
调用将花费不同的时间,因此,您希望在收到MyFileData
后立即处理sequentialOperation
,而不是与另一个同时处理callables
sequentialOperation()
。
您可以通过设置生产者/消费者队列来实现此目的。
生产者是您在示例中执行的public class Main {
private final ExecutorService producerExecutor = Executors.newFixedThreadPool(10);
private final ExecutorService consumerExecutor = Executors.newFixedThreadPool(1);
private final LinkedBlockingQueue<MyData> queue = new LinkedBlockingQueue();//or some other impl
abstract class Producer implements Runnable{
private final File file;
Producer(File file) {
this.file = file;
}
public void run() {
MyData result = someLongAssOperation(file);
queue.offer(result);
}
public abstract void someLongAssOperation(File file);
}
abstract class Consumer implements Runnable {
public void run() {
while (true) {
sequentialOperation(queue.take());
}
}
public abstract void sequentialOperation(MyData data);
}
private void start() {
consumerExecutor.submit(new Consumer(){
//implement sequentialOperation here
});
for (File f : files) {
producerExecutor.submit(new Producer(file) {
//implement the someLongAssOperation()
});
}
}
public static void main(String[] args) {
new Main().start();
}
}
,添加的位用于将结果添加到等待处理的工作队列中。
Consumer是{{1}}调用 - 它在自己的线程中运行,只有一个。所有这个线程都是占用队列的头部并处理它,重复直到程序结束。
这样,您可以最大限度地利用计算机上的所有资源。
包含一些示例代码的相关帖子:Producer/Consumer threads using a Queue
编辑:我想你可能想要一个快速的样本,因为它对任何从未做过它的人都非常不透明
{{1}}