简体   繁体   English

如何在ThreadPoolExecutor中的线程之间分配任务

[英]How to distribute tasks between threads in ThreadPoolExecutor

I have following problem, 我有以下问题,

I have a queue of tasks and there are a lot of types of tasks like: 我有一个任务队列,有很多类型的任务,如:

A, B, C, D, ...

I execute these tasks in thread pool. 我在线程池中执行这些任务。

But I have to restrict same type task execution at same time, hence, this is bad: 但我必须同时限制相同类型的任务执行,因此,这很糟糕:

Thread-1: [A, D, C, B, ...]
Thread-2: [A, C, D, B, ...]

Tasks of type A and B could be executed at same time. 类型A和B的任务可以同时执行。

But this is good: 但这很好:

Thread-1: [A,B,A,B,...]
Thread-2: [C,D,D,C,...]

Hence tasks of same type are always executed sequentially. 因此,相同类型的任务总是按顺序执行。

What is the easiest way to implement this functionality? 实现此功能的最简单方法是什么?

This problem easily can be solved with an actor framework like Akka. 使用像Akka这样的actor框架可以轻松解决这个问题。

For each type of tasks. 对于每种类型的任务。 create an actor. 创造一个演员。

For each separate task, create a message and send it to the actor of corresponding type. 对于每个单独的任务,创建一条消息并将其发送给相应类型的actor。 Messages can be of type Runnable , as they probably are now, and the actor's reaction method can be @Override public void onReceive(Object msg) { ((Runnable)msg).run(); } 消息可以是Runnable类型,因为它们现在可能是,并且actor的反应方法可以是@Override public void onReceive(Object msg) { ((Runnable)msg).run(); } @Override public void onReceive(Object msg) { ((Runnable)msg).run(); }

This way your program will run correctly for any number of threads. 这样,您的程序将针对任意数量的线程正确运行。

I think you can implement your own DistributedThreadPool to control the thread. 我认为你可以实现自己的DistributedThreadPool来控制线程。 It's like some kind of topic subscriber/publisher structure. 这就像某种主题订阅者/发布者结构。

I did a example as following: 我做了如下例子:

class DistributeThreadPool {

Map<String, TypeThread> TypeCenter = new HashMap<String, TypeThread>();

public void execute(Worker command) {
    TypeCenter.get(command.type).accept(command);
}

class TypeThread implements Runnable{

    Thread t = null;
    LinkedBlockingDeque<Runnable> lbq = null;

    public TypeThread() {
        lbq = new LinkedBlockingDeque<Runnable>();
    }

    public void accept(Runnable inRun) {
        lbq.add(inRun);
    }

    public void start() {
        t = new Thread(this);
        t.start();
    }


    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                lbq.take().run();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public DistributeThreadPool(String[] Types) {
    for (String t : Types) {
        TypeThread thread = new TypeThread();
        TypeCenter.put(t, thread);
        thread.start();
    }
}

public static void main(String [] args) {
        DistributeThreadPool dtp = new DistributeThreadPool(new String[] {"AB","CD"});

        Worker w1 = new Worker("AB",()->System.out.println(Thread.currentThread().getName() +"AB"));
        Worker w2 = new Worker("AB",()->System.out.println(Thread.currentThread().getName() +"AB"));
        Worker w3 = new Worker("CD",()->System.out.println(Thread.currentThread().getName() +"CD"));
        Worker w4 = new Worker("CD",()->System.out.println(Thread.currentThread().getName() +"CD"));
        Worker w5 = new Worker("CD",()->System.out.println(Thread.currentThread().getName() +"CD"));

        List<Worker> workers = new ArrayList<Worker>();
        workers.add(w1);
        workers.add(w2);
        workers.add(w3);
        workers.add(w4);
        workers.add(w5);

        workers.forEach(e->dtp.execute(e));
    }
}
CompletableFuture.supplyAsync(this::doTaskA)  
                 .thenAccept(this::useResultFromTaskAinTaskB);

What's happening above is that Task A and the related Task B are actually run in the same thread (one after the other, no need to "get" a new thread to start running Task B). 上面发生的是,任务A和相关的任务B实际上是在同一个线程中运行的(一个接着一个,不需要“获取”一个新的线程来开始运行任务B)。

Or you can use runAsync for Task A if you don't need any information from it, but do need to wait for it to complete before running Task B. 或者,如果您不需要任何信息,可以将runAsync用于任务A,但在运行任务B之前需要等待它完成。

By default, CompletableFuture's will use the common thread pool, but if you want more control over which ThreadPool gets used, you can pass a 2nd argument to the async methods with your own Executor that uses your own ThreadPool. 默认情况下,CompletableFuture将使用公共线程池,但是如果您想要更多地控制使用哪个ThreadPool,您可以使用自己的Executor将第二个参数传递给async方法,该Executor使用您自己的ThreadPool。

Interesting problem. 有趣的问题。 Two questions come to mind: 我想到两个问题:

How many different types of tasks are there? 有多少种不同类型的任务?

If there are relatively few, the simplest way may be to create one thread for each type and assign each incoming task to its kind of thread. 如果相对较少,最简单的方法可能是为每种类型创建一个线程,并将每个传入任务分配给其类型的线程。 As long as tasks are balanced between types (and that's a big assumption) utilization will be good enough. 只要任务在不同类型之间平衡(这是一个很大的假设),利用率就足够了。

What's the expected timeliness/latency for task completion? 任务完成的预期时间/延迟是多少?

If your problem is flexible on the timeliness, you could batch incoming tasks of each kind by count or time interval, submit each batch you retire to the pool, then await completion of batch to submit another of the same kind. 如果您的问题在及时性上是灵活的,您可以按计数或时间间隔批量处理每种类型的传入任务,将您退休的每个批次提交到池中,然后等待完成批次以提交另一种相同类型。

You can adapt the second alternative to batch sizes as small as one, in which case the mechanics of awaiting completion become important for efficiency. 您可以将第二种替代方案调整为小至1的批量大小,在这种情况下,等待完成的机制对于提高效率非常重要。 CompletableFuture would fit the bill here; CompletableFuture适合这里的账单; you could chain the "poll next task of type A and submit to pool" action to the task with thenRunAsync , and fire and forget the task. 您可以使用thenRunAsync “类型A的轮询下一个任务并提交到池”操作到该任务,然后触发并忘记该任务。

You would have to maintain one external task queue per task type; 您必须为每个任务类型维护一个外部任务队列; the work queues of the FJ pool would be for in-progress tasks only. FJ池的工作队列仅用于正在进行的任务。 Still, this design has a good chance of dealing reasonably with imbalance in task count and workload per type. 尽管如此,这种设计很有可能合理地处理任务计数和每种类型工作负载的不平衡。

Hope this helps. 希望这可以帮助。

Implement key ordered executor. 实现密钥有序执行程序。 Each task should have key. 每项任务都应该有关键。 Tasks with same keys will be queued and will be executed successively, tasks with different keys will be executed in parallel. 具有相同键的任务将排队并将连续执行,具有不同键的任务将并行执行。

Implementation in netty 在netty中实施

You can try to make it yourself, but it is tricky and error prone. 您可以尝试自己制作,但它很棘手,容易出错。 I can see few bugs in answer suggested there. 我可以看到那里的答案中有一些错误。

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

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