假设我有5个线程必须对并行蒙特卡罗方法程序进行1,000,000
函数调用的总计。我为5个线程中的每个线程分配了1,000,000 / 5
个函数调用。然而,在经过多次测试(一些测试范围高达1万亿次迭代)后,我意识到有些线程的完成速度比其他线程快得多。因此,我想动态地为每个线程分配工作负载。我的第一个方法涉及一个AtomicLong
变量,它被设置为初始值,比方说,10亿。在每次函数调用之后,我会将AtomicLong
递减1.在每次调用函数之前,程序会检查AtomicLong
是否大于0
,如下所示:
AtomicLong remainingIterations = new AtomicLong(1000000000);
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {//create 5 threads
threadPool.submit(new Runnable() {
public void run() {
while (remainingIterations.get() > 0) {//do a function call if necessary
remainingIterations.decrementAndGet();//decrement # of remaining calls needed
doOneFunctionCall();//perform a function call
}
}
});
}//more unrelated code is not show (thread shutdown, etc.)
这种方法似乎非常慢,我正确使用AtomicLong吗?有更好的方法吗?
答案 0 :(得分:2)
我正确使用AtomicLong吗?
不完全。你使用它的方式,两个线程可以分别检查remainingIterations
,每个线程看1
,然后每个线程递减它,总共-1
。
至于慢速问题,如果doOneFunctionCall()
快速完成,您的应用可能会被AtomicLong周围的锁定争用陷入困境。
关于ExecutorService的好处是它在逻辑上将正在完成的工作与正在执行它的线程分离。您可以提交比线程更多的作业,ExecutorService会尽快执行它们:
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 1000000; i++) {
threadPool.submit(new Runnable() {
public void run() {
doOneFunctionCall();
}
});
}
这可能会在另一个方向上平衡你的工作:创建太多短暂的Runnable对象。您可以尝试查看哪些内容可以在分配工作和快速完成工作之间实现最佳平衡:
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 1000; i++) {
threadPool.submit(new Runnable() {
public void run() {
for (int j = 0; j < 1000; j++) {
doOneFunctionCall();
}
}
});
}
答案 1 :(得分:0)
看看ForkJoinPool。你所尝试的被称为分而治之。在F / J中,您将线程数设置为5.每个线程都有一个待处理任务队列。您可以均匀地设置每个线程/队列的任务数量,当线程用完工作时,它会从另一个线程的队列中窃取工作。这样您就不需要AtomicLong。
有许多使用此类的示例。如果您需要更多信息,请与我们联系。
答案 2 :(得分:0)
避免创建1B任务的一种优雅方法是使用同步队列和ThreadPoolExecutor,这样做会在线程可用之前被阻止。 虽然我没有测试实际表现。
BlockingQueue<Runnable> queue = new SynchronousQueue<>();
ExecutorService threadPool = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
queue);
for (int i = 0; i < 1000000000; i++) {
threadPool.submit(new Runnable() {
public void run() {
doOneFunctionCall();
}
});
}