我正在开发一个程序,它可以执行很长的计算并且必须中断(旅行商问题)。为了获得性能,我希望在运行的计算机上使用尽可能多的线程作为逻辑核心。
我的问题是,我不确定我处理的方式是最好的。每个线程必须返回最佳计算解决方案,直到超时。我的并行代码有大约100行代码,所以我不觉得在线程的主循环中检查线程是否被多次中断是优雅的。
目前,为实现这一目标,我在想这样的事情:
int countThreads = Runtime.getRuntime().availableProcessors();
List<Solution> solutions = new ArrayList<Solution>(countThreads);
final ScheduledExecutorService executor = Executors.newScheduledThreadPool(countThreads + 1);
//Thread that cancel all the other one after the timeout
executor.schedule(new Runnable() {
@Override
public void run() {
executor.shutdownNow();
}
}, timeMax, TimeUnit.SECONDS);
//Threads that compute
for(int i = 0; i < countThreads; i++) {
Solution currentSolution = new Solution();
solutions.add(currentSolution);
ThreadedSolutionFinder solutionFinder =
new ThreadedSolutionFinder(seed, instance, currentSolution);
executor.submit(solutionFinder);
}
// The main thread waits
try {
executor.awaitTermination(timeMax, TimeUnit.SECONDS);
} catch (InterruptedException e) {}
System.out.println("All tasks terminated");
//Iterate over all the found solutions and find the best
//...
我们在这里做的是主线程,为每个线程设置一个解决方案并将其作为参数提供给线程的构造函数。这些线程的run()方法填充给定的解决方案。
但是有许多问题,例如在shutdownNow()命令之后的一个线程,可以继续执行,直到它检查Thread.interrupted(),因此主线程中的awaitTermination()不能持续。当我遍历所有解决方案以找到最佳解决方案时,这意味着并发访问。
我不相信这种设计。你们有些人有一些想法吗?
答案 0 :(得分:0)
将Future
返回的ExecutorService.submit
个实例放入列表中:
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < countThreads; ++i) {
ThreadedSolutionFinder solutionFinder = ...
futures.add(executor.submit(solutionFinder));
}
然后在你想要完成时解决:
long endTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(timeMax);
迭代期货,等待适当的时间,直到结束时间;如果当时尚未完成任务,则取消该任务:
for (Future<?> future : futures) {
if (!future.isDone()) {
long timeout = endTime - System.nanoTime();
if (timeout >= 0) {
try {
future.get(timeout, TimeUnit.NANOSECONDS);
} catch (TimeoutException e) {
// Handle it.
}
}
}
}
最后,取消所有内容(这对已完成的Future
s)没有影响:
for (Future<?> future : futures) {
future.cancel(true);
}
答案 1 :(得分:0)
您需要添加代码来检查每个线程的中断,或者将您的算法划分为更小的部分并运行它们。如果您的超时是软限制,并且您所做的工作很小并且相当规律,那么执行第二个选项可能是可行的。
想象一下TSP的天真实施,您可以让每个可运行的步骤找到每个当前解决方案的下一个最近的城市:
CompletionService<PartialSolution> comp = new ExecutorCompletionService<>(executor);
// Submit initial runners
comp.submit(new PartialSolutionTask());
...
long current = System.currentTimeMillis();
// Only wait 30 total seconds
final long end = current + TimeUnit.SECONDS.toMillis(30);
while (current < end) {
Future<PartialSolution> f = comp.poll(end - current, TimeUnit.MILLISECONDS);
if (f == null) {
// Timeout
break;
}
// Submit a new task using the results of the previous task
comp.submit(new PartialSolutionTask(f.get()));
current = System.currentTimeMillis();
}
// Still should have the same number of running tasks so wait for them to finish by calling comp.poll() N times
不幸的是,你会超过时间限制,有些人会等待最后一次完成,但如果他们的运行时间足够小,它应该是可行的。
答案 2 :(得分:0)
实际上,我发现了另一种解决方案(我认为不比你的更差或更好):
public Solution executeThreads() {
List<Solution> solutions = new ArrayList<Solution>(countThreads);
List<Callable<Object>> threads = new ArrayList<Callable<Object>>(countThreads);
final ScheduledExecutorService executor = Executors.newScheduledThreadPool(countThreads + 1);
//Thread that cancel all the other one after the timeout
executor.schedule(new Runnable() {
public void run() {
executor.shutdownNow();
}
}, timeMax, TimeUnit.SECONDS);
//Threads that compute
for(int i = 0; i < countThreads; i++) {
Solution currentSolution = new Solution();
solutions.add(currentSolution);
ThreadedSolutionFinder solutionFinder =
new ThreadedSolutionFinder(i, instance, currentSolution);
threads.add(Executors.callable(solutionFinder));
}
long startTime = System.currentTimeMillis();
/* Execute and then wait that all threads have finished */
try {
executor.invokeAll(threads);
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
long time = System.currentTimeMillis() - startTime;
System.out.println("---------------------------------------");
System.out.println("All tasks terminated in " + time / 1000.f + "s");
//Iterate over all the found solutions
//...
在ThreadedSolutionFinder中:
@Override
public void run() {
thread = Thread.currentThread();
solution.setOF(Double.MAX_VALUE); //No solution so it has the higher cost
do {
Solution randomSolution = generateRandomSolution();
Solution localSearchSol = localSearch(randomSolution);
System.out.println("[Worker " + workerId +
"] " + randomSolution.getOF() + "\t-> " + localSearchSol.getOF());
if(localSearchSol.getOF() < solution.getOF()) {
//Copy the elements because the solution reference must not change
solution.clear();
solution.addAll(localSearchSol);
solution.setOF(localSearchSol.getOF());
}
} while(!thread.isInterrupted());
}
private Solution generateRandomSolution() {
Solution solution = new Solution();
/* We add all the city (index) to the solution,
* no more things are required. TSPCostCalculator
* do the trick */
for(int i = 0; i < instance.getN(); i++) {
solution.add(i);
}
//Randomize the solution indices (cities)
Collections.shuffle(solution, ThreadLocalRandom.current());
//Compute the efficiency of the solution
solution.setOF(TSPCostCalculator.calcOF(instance, solution));
return solution;
}
/* Return the best solution among many changed solution
* (local search algorithm)
* @param generatedSolution The solution to begin with
* @return the best solution found with the algorithm,
* null if no better solution
*/
private Solution localSearch(Solution solution) {
boolean continueExploration = true;
Solution bestSolution = solution;
while(continueExploration && !thread.isInterrupted())
{
Solution swapSolution;
swapSolution = exploreNeighborhood(bestSolution);
//TODO: Solve this, computeSwapCost is inaccurate
if((float)swapSolution.getOF() < (float)bestSolution.getOF())
bestSolution = swapSolution;
else
continueExploration = false;
}
return bestSolution;
}
/* Return the best solution among many changed solution
* (local search algorithm)
* @param generatedSolution The solution to begin with
* @return the best solution found with the algorithm
*/
private Solution exploreNeighborhood(Solution solution) {
Solution bestSolution = solution;
Solution swapSolution = solution.clone();
for(int i = 0; i < solution.size() && !thread.isInterrupted(); i++)
{
for(int j = i + 1; j < solution.size() && !thread.isInterrupted(); j++)
{
double costBefore = swapSolution.getOF();
double relativeCostBefore = computeSwapCost(swapSolution, i, j);
swapSolution.swap(i, j);
double relativeCostAfter = computeSwapCost(swapSolution, i, j);
double diffCost = relativeCostBefore - relativeCostAfter;
swapSolution.setOF(costBefore - diffCost);
if(swapSolution.getOF() < bestSolution.getOF())
bestSolution = swapSolution.clone();
}
}
return bestSolution;
}
}
在大城市(例如10k)上,程序立即停止。唯一的问题(可能与我们所说的设计无关)是即使在4个物理内核上,2或3个线程也能提供最佳的TSP解决方案。超过2或3的事实,在所有线程中给出了更糟糕的解决方案(非常奇怪,因为没有同步,我无法解释)。