简体   繁体   English

如何限制创建的线程数并等待主线程,直到任何一个线程找到答案?

[英]How to limit number of threads created and wait main thread until any one thread finds answer?

This is the code to find the first pair of numbers (except 1) whose sum of LCM and HCF is equal to the number.这是查找 LCM 和 HCF 之和等于该数字的第一对数字(1 除外)的代码。

import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

class PerfectPartition {
    static long gcd(long a, long b) {
        if (a == 0)
            return b;
        return gcd(b % a, a);
    }

    // method to return LCM of two numbers
    static long lcm(long a, long b) {
        return (a / gcd(a, b)) * b;
    }

    long[] getPartition(long n) {
        var ref = new Object() {
            long x;
            long y;
            long[] ret = null;
        };

        Thread mainThread = Thread.currentThread();
        ThreadGroup t = new ThreadGroup("InnerLoop");

        for (ref.x = 2; ref.x < (n + 2) / 2; ref.x++) {
            if (t.activeCount() < 256) {

                new Thread(t, () -> {
                    for (ref.y = 2; ref.y < (n + 2) / 2; ref.y++) {
                        long z = lcm(ref.x, ref.y) + gcd(ref.x, ref.y);
                        if (z == n) {
                            ref.ret = new long[]{ref.x, ref.y};

                            t.interrupt();
                            break;
                        }
                    }
                }, "Thread_" + ref.x).start();

                if (ref.ret != null) {
                    return ref.ret;
                }
            } else {
                ref.x--;
            }
        }//return new long[]{1, n - 2};

        return Objects.requireNonNullElseGet(ref.ret, () -> new long[]{1, n - 2});
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long n = sc.nextLong();
        long[] partition = new PerfectPartition().getPartition(n);
        System.out.println(partition[0] + " " + partition[1]);
    }
}

I want to stop the code execution as soon as the first pair is found.我想在找到第一对后立即停止代码执行。 But instead, the main thread just keeps running and prints 1 and n-1 .但相反, main线程只是继续运行并打印1n-1
What could be an optimal solution to limit the no.什么可能是限制数量的最佳解决方案。 of threads (<256 as the range of n is 2 to max of long )?线程数(<256,因为 n 的范围是2max of long )?

Expected Output ( n=4 ): 2 2预期 Outputn=4 ):2 2
Expected Output ( n=8 ): 4 4预期 Outputn=8 ):4 4

What could be an optimal solution to limit the no.什么可能是限制数量的最佳解决方案。 of threads (<256 as the range of n is 2 to max of long)?线程数(<256,因为 n 的范围是 2 到最大长度)?

First, you should consider the hardware where the code will be executed ( eg, the number of cores) and the type of algorithm that you are parallelizing, namely is it CPU-bound ?, memory-bound ?, IO-bound , and so on.首先,您应该考虑将执行代码的硬件(例如,内核数量)和您正在并行化的算法类型,即它是CPU 密集型内存密集型IO 密集型等上。

Your code is CPU-bound , therefore, from a performance point of view, typically does not payoff having more threads running than the number of available cores in the system.您的代码受 CPU 限制,因此,从性能的角度来看,运行的线程数通常不会超过系统中可用内核的数量。 As is always the case profile as much as you can.与往常一样,尽可能多地介绍情况。

Second, you need to distribute work among threads in a way that justifies the parallelism, in your case:其次,在您的情况下,您需要以一种证明并行性合理的方式在线程之间分配工作:

  for (ref.x = 2; ref.x < (n + 2) / 2; ref.x++) {
        if (t.activeCount() < 256) {

            new Thread(t, () -> {
                for (ref.y = 2; ref.y < (n + 2) / 2; ref.y++) {
                    long z = lcm(ref.x, ref.y) + gcd(ref.x, ref.y);
                    if (z == n) {
                        ref.ret = new long[]{ref.x, ref.y};

                        t.interrupt();
                        break;
                    }
                }
            }, "Thread_" + ref.x).start();

            if (ref.ret != null) {
                return ref.ret;
            }
        } else {
            ref.x--;
        }
    }//return new long[]{1, n - 2};

which you kind of did, however IMO in a convoluted way;您确实做到了,但是IMO以一种令人费解的方式; much easier IMO is to parallelize the loop explicitly, ie, splitting its iterations among threads, and remove all the ThreadGroup related logic.更简单的 IMO 是显式地并行化循环,在线程之间拆分其迭代,并删除所有与ThreadGroup相关的逻辑。

Third, lookout for race-conditions such as:第三,注意竞争条件,例如:

var ref = new Object() {
    long x;
    long y;
    long[] ret = null;
};

this object is shared among threads, and updated by them, consequently leading to race-conditions.这个 object 在线程之间共享,并由它们更新,从而导致竞争条件。 As we are about to see you do not actually need such a shared object anyway.正如我们即将看到的那样,无论如何您实际上并不需要这样的共享 object。

So let us do this step by step:所以让我们一步一步来:

First, find out the number of threads that you should execute the code with i.e., the same number of threads as cores:首先,找出执行代码时应使用的线程数,与内核数相同的线程数:

int cores = Runtime.getRuntime().availableProcessors();

Define the parallel work (this a possible example of a loop distribution):定义并行工作(这是循环分布的一个可能示例):

public void run() {
    for (int x = 2; && x < (n + 2) / 2; x ++) {
        for (int y = 2 + threadID; y < (n + 2) / 2; y += total_threads) {
            long z = lcm(x, y) + gcd(x, y);
            if (z == n) {
                // do something 
            }
        }
    }
}

in the code below, we split the work to be done in parallel in a round-robin fashion among threads as showcased in the image below:在下面的代码中,我们在线程之间以循环方式并行拆分要完成的工作,如下图所示:

在此处输入图像描述

I want to stop the code execution as soon as the first pair is found.我想在找到第一对后立即停止代码执行。

There are several ways of achieving this.有几种方法可以实现这一点。 I will provide the simplest IMO, albeit not the most sophisticated .我将提供最简单的 IMO,虽然不是最复杂的. You can use a variable to signal to the threads when the result was already found, for instance:当已经找到结果时,您可以使用变量向线程发出信号,例如:

final AtomicBoolean found;

each thread will share the same AtomicBoolean variable so that the change performed in one of them are also visible to the others:每个线程将共享相同的AtomicBoolean变量,以便在其中一个线程中执行的更改对其他线程也可见:

@Override
public void run() {
    for (int x = 2 ; !found.get() && x < (n + 2) / 2; x ++) {
        for (int y = 2 + threadID; y < (n + 2) / 2; y += total_threads)  {
            long z = lcm(x, y) + gcd(x, y);
            if (z == n) {
                synchronized (found) {
                    if(!found.get()) {
                        rest[0] = x;
                        rest[1] = y;
                        found.set(true);
                    }
                    return;
                }
            }
        }
    }
}

Since you were asking for a code snippet example here is a simple non-bulletproof (and not properly tested) running coding example:由于您要的是代码片段示例,因此这里是一个简单的非防弹(且未经过适当测试)运行编码示例:

class ThreadWork implements Runnable{

    final long[] rest;
    final AtomicBoolean found;
    final int threadID;
    final int total_threads;
    final long n;

    ThreadWork(long[] rest, AtomicBoolean found, int threadID, int total_threads, long n) {
        this.rest = rest;
        this.found = found;
        this.threadID = threadID;
        this.total_threads = total_threads;
        this.n = n;
    }

    static long gcd(long a, long b) {
        return (a == 0) ? b : gcd(b % a, a);
    }

    static long lcm(long a, long b, long gcd) {
        return (a / gcd) * b;
    }

    @Override
    public void run() {
        for (int x = 2; !found.get() && x < (n + 2) / 2; x ++) {
            for (int y = 2 + threadID; !found.get() && y < (n + 2) / 2; y += total_threads) {
                long result = gcd(x, y);
                long z = lcm(x, y, result) + result;
                if (z == n) {
                    synchronized (found) {
                        if(!found.get()) {
                            rest[0] = x;
                            rest[1] = y;
                            found.set(true);
                        }
                        return;
                    }
                }
            }
        }
    }
}

class PerfectPartition {

    public static void main(String[] args) throws InterruptedException {
        Scanner sc = new Scanner(System.in);
        final long n = sc.nextLong();
       final int total_threads = Runtime.getRuntime().availableProcessors();

        long[] rest = new long[2];
        AtomicBoolean found = new AtomicBoolean();

        double startTime = System.nanoTime();
        Thread[] threads = new Thread[total_threads];
        for(int i = 0; i < total_threads; i++){
            ThreadWork task = new ThreadWork(rest, found, i, total_threads, n);
            threads[i] = new Thread(task);
            threads[i].start();
        }

        for(int i = 0; i < total_threads; i++){
            threads[i].join();
        }

        double estimatedTime = System.nanoTime() - startTime;
        System.out.println(rest[0] + " " + rest[1]);


        double elapsedTimeInSecond = estimatedTime / 1_000_000_000;
        System.out.println(elapsedTimeInSecond + " seconds");
    }
}

OUTPUT: OUTPUT:

4 -> 2 2
8 -> 4 4

Used this code as inspiration to come up with your own solution that best fits your requirements.以此代码为灵感,提出最适合您要求的解决方案。 After you fully understand those basics, try to improve the approach with more sophisticated Java features such as Executors , Futures , CountDownLatch .在您完全了解这些基础知识后,尝试使用更复杂的 Java 功能改进方法,例如ExecutorsFuturesCountDownLatch


NEW UPDATE: Sequential Optimization新更新:顺序优化

Looking at the gcd method:查看gcd方法:

  static long gcd(long a, long b) {
        return (a == 0)? b : gcd(b % a, a);
  }

and the lcm method:lcm方法:

static long lcm(long a, long b) {
    return (a / gcd(a, b)) * b;
}

and how they are being used:以及它们的使用方式:

long z = lcm(ref.x, ref.y) + gcd(ref.x, ref.y);

you can optimize your sequential code by not calling again gcd(a, b) in the lcm method.您可以通过不在lcm方法中再次调用gcd(a, b)来优化您的顺序代码。 So change lcm method to:所以将 lcm 方法更改为:

static long lcm(long a, long b, long gcd) {
    return (a / gcd) * b;
}

and

long z = lcm(ref.x, ref.y) + gcd(ref.x, ref.y);

to

long result = gcd(ref.x, ref.y)
long z = lcm(ref.x, ref.y, gcd) + gcd;

The code that I have provided in this answer already reflects those changes.我在这个答案中提供的代码已经反映了这些变化。

First of all, you miss calling "start" on the thread.首先,您错过了在线程上调用“start”。

new Thread(t, () -> {
    ...
    ...
}, "Thread_" + ref.x).start();

And coming to your question, to limit number the of threads you can use thread pools, for example, Executors.newFixedThreadPool(int nThreads).并提出您的问题,以限制您可以使用线程池的线程数,例如 Executors.newFixedThreadPool(int nThreads)。

And to stop executing you can have your main thread wait on a single count CountDownLatch and count down the latch when there is a successful match in your worker thread and in the main shutdown the thread pool when the wait on the latch completes.要停止执行,您可以让主线程等待单个计数 CountDownLatch 并在您的工作线程中成功匹配时倒计时锁存器,并在锁存器等待完成时关闭线程池。

As you asked, here is a sample code that uses thread pools and CountDownLatch:正如您所问的,这是一个使用线程池和 CountDownLatch 的示例代码:

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class LcmHcmSum {

    static long gcd(long a, long b) {
        if (a == 0)
            return b;
        return gcd(b % a, a);
    }

    // method to return LCM of two numbers
    static long lcm(long a, long b) {
        return (a / gcd(a, b)) * b;
    }
    
    long[] getPartition(long n) {
        singleThreadJobSubmitter.execute(() -> {
            for (int x = 2; x < (n + 2) / 2; x++) {
                    submitjob(n, x);
                    if(numberPair != null) break;  // match found, exit the loop
            }
            try {
                jobsExecutor.shutdown();  // process the already submitted jobs
                jobsExecutor.awaitTermination(10, TimeUnit.SECONDS);  // wait for the completion of the jobs
                
                if(numberPair == null) {  // no match found, all jobs processed, nothing more to do, count down the latch 
                    latch.countDown();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        try {
            latch.await();
            singleThreadJobSubmitter.shutdownNow();
            jobsExecutor.shutdownNow();
            
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        return Objects.requireNonNullElseGet(numberPair, () -> new long[]{1, n - 2});
    }

    private Future<?> submitjob(long n, long x) {
        return jobsExecutor.submit(() -> {
            for (int y = 2; y < (n + 2) / 2; y++) {
                long z = lcm(x, y) + gcd(x, y);
                if (z == n) {
                    synchronized(LcmHcmSum.class) {  numberPair = new long[]{x, y}; }
                    latch.countDown();
                    break;
                }
            }
        });
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long n = sc.nextLong();
        long[] partition = new LcmHcmSum().getPartition(n);
        System.out.println(partition[0] + " " + partition[1]);
    }
    
    private static CountDownLatch latch = new CountDownLatch(1);
    private static ExecutorService jobsExecutor = Executors.newFixedThreadPool(4);
    private static volatile long[] numberPair = null;
    private static ExecutorService singleThreadJobSubmitter = Executors.newSingleThreadExecutor();      
    

}

You can use a ThreadPool.您可以使用线程池。 Something like:就像是:

ExecutorService executor = Executors.newFixedThreadPool(256);

And then schedule tasks (or runnables) into it.然后将任务(或可运行文件)安排到其中。

Once you are done, stop adding tasks, and terminate the thread pool (terminate will also block the ability of adding new tasks to the threadpool).完成后,停止添加任务,并终止线程池(终止也会阻止向线程池添加新任务的能力)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM