Java:实现多线程供应商/消费者管道,每种任务具有并行限制
我们需要异步处理不同类型的对象。每种类型的对象都使用API密钥进行处理Java:实现多线程供应商/消费者管道,每种任务具有并行限制,java,threadpool,producer-consumer,Java,Threadpool,Producer Consumer,我们需要异步处理不同类型的对象。每种类型的对象都使用API密钥进行处理 Semaphore key1Semaphore = new Semaphore(2); Semaphore key2Semaphore = new Semaphore(3); 每个API密钥对并发使用都有自己的限制(比如一个API密钥不超过5个并行会话) 我们有工作线程数的全局限制(CPU限制) 我们希望在工作线程限制内进行尽可能多的API调用 可能的解决办法: 2 tasks with KEY1 (max 2 sessi
Semaphore key1Semaphore = new Semaphore(2);
Semaphore key2Semaphore = new Semaphore(3);
每个API密钥对并发使用都有自己的限制(比如一个API密钥不超过5个并行会话)
我们有工作线程数的全局限制(CPU限制)
我们希望在工作线程限制内进行尽可能多的API调用
可能的解决办法:
2 tasks with KEY1 (max 2 session) -\ total 3 workers
5 tasks with KEY2 (max 3 session) -/
3 tasks with KEY1 (max 1 session) & 3 workers
它们是:
可能的解决办法:
2 tasks with KEY1 (max 2 session) -\ total 3 workers
5 tasks with KEY2 (max 3 session) -/
3 tasks with KEY1 (max 1 session) & 3 workers
是:
执行顺序并不重要(但我们希望听到类似于先入先出的策略),最大吞吐量是最重要的
不清楚选择哪种实施战略
ThreadExecutor
和任何队列都是不够的,因为您需要知道ThreadExecutor
当前使用了哪些API键,我不确定我是否答对了,但您需要的似乎是每个API键的一个
Semaphore key1Semaphore = new Semaphore(2);
Semaphore key2Semaphore = new Semaphore(3);
您可以通过调用key1Semaphore.tryAcquire()
来检查key1Semaphore
是否有许可证,如果有许可证,可以获取许可证。这是非阻塞的,因此如果它失败并返回false,您可以尝试从另一个API键获取信号量并从该API键提交任务
重要的是,在使用其中一个API键的任务结束时,信号量许可被释放回来
您可能需要一个额外的对象来与wait()
和notify()
同步,以便在任务完成时通知正在分派任务的主线程再次检查信号量
因此,从本质上讲,您的任务调度器将向您的ExecutorService
提交5个任务(共3个工作人员),然后在其中一个信号量许可证被释放之前,它将无法再提交任何任务
当任务完成且许可证被释放时,调度程序将收到通知,因此它将取消等待的阻塞,并再次按顺序检查信号量,并将任务提交给执行器服务
这个解决方案有点偏向于第一个API键,但是您可以通过检查每个键的任务长度并更公平地分配它们来进一步改进它。您甚至可以旋转索引,这样每次循环都会将索引增加1,这样第一次从API键1开始,第二次从API键2开始,等等。我可能会创建一个服务
- 单个
,包含由任务和相应键组成的条目队列
- 带有键的
,以及该键的已运行线程(Map
),以及Map
- 具有全局允许的线程计数的
ThreadPoolExecutor
Runnable
,它将额外增加1。减少映射和2中的关键线程数。触发队列的重新评估,以便提交新任务(如果适用)
还可以在
BlockingQueue
中创建“每个键的活动计数”逻辑,该逻辑将返回first()
下一个元素,该元素包含尚未达到最大计数的键的任务,并将其作为管理队列传递给ThreadPoolExecutor
构造函数;但我相信这会破坏队列契约,使用起来也不完全安全。为什么不在每个密钥上使用一个执行器,并在前面使用一个多路复用器,将每个任务分配给正确的执行器?我有200个API密钥,并且对CPU有限制。我不能仅仅用5个线程创建200个线程池,然后根据API密钥选择一个线程池。。。从两个方面来看,任务都很有趣。哦,所以您基本上想要一个具有全局线程数的主执行器服务,根据给定键的多少任务已经处于活动状态,使用一种策略来填充它?您可以看到我所担心的一切。)在简单情况下,队列用作延迟任务的临时存储,但在我的情况下,next()
方法应该知道线程执行器的当前状态。问题是我不能使用线程池、执行器或队列的任何高级实现,需要从低级块构建解决方案。我考虑使用相同的方法,围绕原始任务向Runnable
添加周围逻辑,并重用ThreadPoolExecutor
。至少这样我就不用实现多线程代码了。棘手的部分是将任务返回到队列(自引用可能会导致“振荡”/临时100%CPU负载循环)。@gavenkoa是的,在我确定可以提交任务之前,我不会将任务从队列中取出,即迭代直到找到有效的任务,并将其删除。我想,这确实打消了使用队列的初衷。