简体   繁体   English

Java中的线程安全队列和“master / worker”程序的模式/原则

[英]Patterns/Principles for thread-safe queues and “master/worker” program in Java

I have a problem which I believe is the classic master/worker pattern, and I'm seeking advice on implementation. 我有一个问题,我认为是经典的主/工模式,我正在寻求实施方面的建议。 Here's what I currently am thinking about the problem: 以下是我目前正在考虑的问题:

There's a global "queue" of some sort, and it is a central place where "the work to be done" is kept. 有一种全球性的“队列”,它是一个“保持工作”的中心位置。 Presumably this queue will be managed by a kind of "master" object. 据推测,这个队列将由一种“主”对象管理。 Threads will be spawned to go find work to do, and when they find work to do, they'll tell the master thing (whatever that is) to "add this to the queue of work to be done". 线程将被生成以找到要做的工作,当他们找到要做的工作时,他们会告诉主要事物(无论是什么)“将其添加到要完成的工作队列”。

The master, perhaps on an interval, will spawn other threads that actually perform the work to be done. 主人,也许是间隔,将产生实际执行工作的其他线程。 Once a thread completes its work, I'd like it to notify the master that the work is finished. 一旦线程完成其工作,我希望它通知主服务器工作已完成。 Then, the master can remove this work from the queue. 然后,主服务器可以从队列中删除此工作。

I've done a fair amount of thread programming in Java in the past, but it's all been prior to JDK 1.5 and consequently I am not familiar with the appropriate new APIs for handling this case. 我以前在Java中完成了大量的线程编程,但它们都先于JDK 1.5,因此我不熟悉处理这种情况的相应新API。 I understand that JDK7 will have fork-join, and that that might be a solution for me, but I am not able to use an early-access product in this project. 我知道JDK7会有fork-join,这对我来说可能是一个解决方案,但是我无法在这个项目中使用早期访问产品。

The problems, as I see them, are: 我认为问题是:

1) how to have the "threads doing the work" communicate back to the master telling them that their work is complete and that the master can now remove the work from the queue 1)如何让“线程完成工作”与主人沟通,告诉他们他们的工作已经完成,主人现在可以从队列中删除工作

2) how to efficiently have the master guarantee that work is only ever scheduled once. 2)如何有效地保证工作只安排一次。 For example, let's say this queue has a million items, and it wants to tell a worker to "go do these 100 things". 例如,假设这个队列有一百万个项目,它想告诉一个工人“去做这100件事”。 What's the most efficient way of guaranteeing that when it schedules work to the next worker, it gets "the next 100 things" and not "the 100 things I've already scheduled"? 什么是最有效的方法来保证当它为下一个工人安排工作时,它会得到“接下来的100件事”而不是“我已经安排的100件事”?

3) choosing an appropriate data structure for the queue. 3)为队列选择适当的数据结构。 My thinking here is that the "threads finding work to do" could potentially find the same work to do more than once, and they'd send a message to the master saying "here's work", and the master would realize that the work has already been scheduled and consequently should ignore the message. 我在这里的想法是,“寻找工作要做的线程”可能会发现不止一次做同样的工作,并且他们会向主人发送一条消息说“这里的工作”,并且主人会意识到工作已经已经安排好,因此应该忽略该消息。 I want to ensure that I choose the right data structure such that this computation is as cheap as possible. 我想确保选择正确的数据结构,以便这种计算尽可能便宜。

Traditionally, I would have done this in a database, in sort of a finite-state-machine manner, working "tasks" through from start to complete. 传统上,我会在数据库中以有限状态机方式完成此任务,从开始到完成工作“任务”。 However, in this problem, I don't want to use a database because of the high volume and volatility of the queue. 但是,在这个问题中,我不想使用数据库,因为队列的数量和波动性很大。 In addition, I'd like to keep this as light-weight as possible. 另外,我想尽量保持它的重量轻。 I don't want to use any app server if that can be avoided. 如果可以避免,我不想使用任何应用服务器。

It is quite likely that this problem I'm describing is a common problem with a well-known name and accepted set of solutions, but I, with my lowly non-CS degree, do not know what this is called (ie please be gentle). 很有可能我所描述的这个问题是一个众所周知的名称和一套公认的解决方案的常见问题,但我,我的低级非CS学位,不知道这叫什么(即请温柔)。

Thanks for any and all pointers. 感谢任何和所有指针。

As far as I understand your requirements, you need ExecutorService . 据我所知,您需要ExecutorService ExecutorService have ExecutorService有

submit(Callable task)

method which return value is Future . 返回值的方法是Future Future is a blocking way to communicate back from worker to master. 未来是一种阻碍从工人到主人沟通的方式。 You could easily expand this mechanism to work is asynchronous manner. 您可以轻松地扩展此机制以异步方式工作。 And yes, ExecutorService also maintaining work queue like ThreadPoolExecutor. 是的,ExecutorService还像ThreadPoolExecutor一样维护工作队列。 So you don't need to bother about scheduling, in most cases. 因此,在大多数情况下,您不需要为调度而烦恼。 java.util.concurrent package already have efficient implementations of thread safe queue (ConcurrentLinked queue - nonblocking, and LinkedBlockedQueue - blocking). java.util.concurrent包已经有了线程安全队列的高效实现(ConcurrentLinked queue - nonblocking和LinkedBlockedQueue - blocking)。

Check out java.util.concurrent in the Java library. 查看Java库中的java.util.concurrent

Depending on your application it might be as simple as cobbling together some blocking queue and a ThreadPoolExecutor. 根据您的应用程序,它可能就像将一些阻塞队列和一个ThreadPoolExecutor拼凑在一起一样简单。

Also, the book Java Concurrency in Practice by Brian Goetz might be helpful. 此外,Brian Goetz撰写的Java Concurrency in Practice一书可能会有所帮助。

First, why do you want to hold the items after a worker started doing them? 首先,为什么你想在工人开始做之后拿着这些物品? Normally, you would have a queue of work and a worker takes items out of this queue. 通常情况下,您将拥有一个工作队列,并且工作人员将项目从此队列中取出。 This would also solve the "how can I prevent workers from getting the same item"-problem. 这也将解决“我怎样才能防止工人获得相同的项目”问题。

To your questions: 对你的问题:

1) how to have the "threads doing the work" communicate back to the master telling them that their work is complete and that the master can now remove the work from the queue 1)如何让“线程完成工作”与主人沟通,告诉他们他们的工作已经完成,主人现在可以从队列中删除工作

The master could listen to the workers using the listener/observer pattern 主人可以使用监听器/观察者模式监听工作人员

2) how to efficiently have the master guarantee that work is only ever scheduled once. 2)如何有效地保证工作只安排一次。 For example, let's say this queue has a million items, and it wants to tell a worker to "go do these 100 things". 例如,假设这个队列有一百万个项目,它想告诉一个工人“去做这100件事”。 What's the most efficient way of guaranteeing that when it schedules work to the next worker, it gets "the next 100 things" and not "the 100 things I've already scheduled"? 什么是最有效的方法来保证当它为下一个工人安排工作时,它会得到“接下来的100件事”而不是“我已经安排的100件事”?

See above. 往上看。 I would let the workers pull the items out of the queue. 我会让工人把物品拉出队列。

3) choosing an appropriate data structure for the queue. 3)为队列选择适当的数据结构。 My thinking here is that the "threads finding work to do" could potentially find the same work to do more than once, and they'd send a message to the master saying "here's work", and the master would realize that the work has already been scheduled and consequently should ignore the message. 我在这里的想法是,“寻找工作要做的线程”可能会发现不止一次做同样的工作,并且他们会向主人发送一条消息说“这里的工作”,并且主人会意识到工作已经已经安排好,因此应该忽略该消息。 I want to ensure that I choose the right data structure such that this computation is as cheap as possible. 我想确保选择正确的数据结构,以便这种计算尽可能便宜。

There are Implementations of a blocking queue since Java 5 自Java 5以来,存在阻塞队列的实现

Don't forget Jini and Javaspaces. 不要忘记Jini和Javaspaces。 What you're describing sounds very like the classic producer/consumer pattern that space-based architectures excel at. 您所描述的内容听起来非常像天基架构所擅长的经典制作人/消费者模式。

A producer will write the jobs into the space. 制片人将作业写入空间。 1 or more consumers will take out jobs (under a transaction) and work on that in parallel, and then write the results back. 一个或多个消费者将取出工作(在交易下)并并行处理,然后将结果写回来。 Since it's under a transaction, if a problem occurs the job is made available again for another consumer . 由于它处于交易之下,如果出现问题,则该作业将再次供另一个消费者使用。

You can scale this trivially by adding more consumers. 您可以通过添加更多消费者来轻松扩展。 This works especially well when the consumers are separate VMs and you scale across the network. 当消费者是独立的虚拟机并且您可以跨网络扩展时,这尤其有效。

If you are open to the idea of Spring, then check out their Spring Integration project. 如果您对Spring的想法持开放态度,那么请查看他们的Spring Integration项目。 It gives you all the queue/thread-pool boilerplate out of the box and leaves you to focus on the business logic. 它为您提供了开箱即用的所有队列/线程池样板,让您专注于业务逻辑。 Configuration is kept to a minimum using @annotations. 使用@annotations将配置保持在最低限度。

btw, the Goetz is very good. 顺便说一句,Goetz非常好。

This doesn't sound like a master-worker problem, but a specialized client above a threadpool. 这听起来不像是一个主工作者问题,而是一个专注于线程池的客户端。 Given that you have a lot of scavenging threads and not a lot of processing units, it may be worthwhile simply doing a scavaging pass and then a computing pass. 鉴于你有很多清理线程而不是很多处理单元,所以简单地做一个scavaging pass然后计算传递可能是值得的。 By storing the work items in a Set, the uniqueness constraint will remove duplicates. 通过将工作项存储在Set中,唯一性约束将删除重复项。 The second pass can submit all of the work to an ExecutorService to perform the process in parallel. 第二遍可以将所有工作提交给ExecutorService以并行执行该过程。

A master-worker model generally assumes that the data provider has all of the work and supplies it to the master to manage. 主工作者模型通常假定数据提供者具有所有工作并将其提供给主人来管理。 The master controls the work execution and deals with distributed computation, time-outs, failures, retries, etc. A fork-join abstraction is a recursive rather than iterative data provider. 主控制工作执行并处理分布式计算,超时,失败,重试等。分叉连接抽象是一种递归而非迭代的数据提供者。 A map-reduce abstraction is a multi-step master-worker that is useful in certain scenarios. map-reduce抽象是一个多步骤的master-worker,在某些场景中很有用。

A good example of master-worker is for trivially parallel problems, such as finding prime numbers. 主工作者的一个很好的例子是平凡的并行问题,例如查找素数。 Another is a data load where each entry is independant (validate, transform, stage). 另一个是数据加载,其中每个条目都是独立的(验证,转换,阶段)。 The need to process a known working set, handle failures, etc. is what makes a master-worker model different than a thread-pool. 处理已知工作集,处理故障等的需要是使主工作者模型与线程池不同的原因。 This is why a master must be in control and pushes the work units out, whereas a threadpool allows workers to pull work from a shared queue. 这就是为什么master必须在控制中并将工作单元推出,而threadpool允许worker从共享队列中提取工作。

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

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