我有一个队列,生产者将在其中提交任务。每个任务都有一个有效负载和一个文件路径。在消费者方面,我有一个 Executor 线程池,它应该从队列中提取任务并将它们分配给线程。以下是一些需要遵循的约束条件:
这是问题陈述的示例。让我们假设以下是队列的状态。这里 T1 是线程名称,括号包含文件路径。
head -> [T1(A),T2(B),T3(C),T4(A),T5(B),T6(A),T7(A),T8(C)] <- tail
因此我们可以让三个线程同时处理文件路径 A
、B
和 C
的任务。但是,以下不应该发生。这里文件路径 A
有两个线程同时处理它。
Pool1-Thread1-T4(A)
Pool1-Thread2-T5(B)
Pool1-Thread3-T6(A)
文件路径的数量可以介于 10k-50k
为了解决这个问题,我想出的方法是有两个映射,即 Map<String, Queue<Tasks>>
用于任务队列和 Map<String, AtomicBoolean>
用于令牌,两者都有针对 file 的键路径。一个正在运行的消费者线程,它将不断迭代 Tasks Map
,并且需要在为该路径创建线程之前获取该文件路径的标记(布尔值应为 true)。线程完成后,它会返回令牌(通过将布尔值标记回 true
)。
问题
编辑
Tasks Map
。如果它找到任何可用的任务和令牌,它就会从执行器生成一个新线程。答案 0 :(得分:0)
我会避免使用 Executor 线程池并使用以下方法:
一种常见的方法是将所有队列放在一个数组中,然后根据某个值以队列数为模进行分配,这就是调用分区,例如:
queues = new Queue[N];
typeOfTask = task.getType() (to int somehow, for example you can hash or use the char if it is a single letter)
queueIndex = typeTask % queues.length
queues[queueIndex].push(task)
最后一点,在使用文件系统时,我会避免使用多个线程(如果有的话),因为磁盘是...一个。因此,并行处理它没有什么意义,除非您必须写入大量不想缓冲的数据。相反,线程更多地是为了能够使用更多处理器并行工作以提高速度或在执行阻塞操作时。
答案 1 :(得分:0)
我已经尝试实现消费者用例的一部分,似乎问题多于解决方案。请阅读代码中的内嵌注释。
public class Consumer implements Runnable {
private volatile boolean isStopped;
private final Map<String, Queue<Task>> tasks;
private final Map<String, AtomicBoolean> tokens;
//Preferably FixedThreadService
private final ExecutorService service;
public Consumer(Map<String, Queue<Task>> tasks, Map<String, AtomicBoolean> tokens, ExecutorService service) {
this.tasks = tasks;
this.tokens = tokens;
this.service = service;
isStopped = false;
}
public void stopConsumer(){
isStopped = true;
}
@Override
public void run() {
while(!isStopped){
// tasks map should have latest values because producer is going to update this map
// frequently. Should have to use ConcurrentHashMap.
Set<String> keys = tasks.keySet();
for ( String key:tasks.keySet() ) {
if(!tokens.get(key).get()){
continue;
}
try {
//This does not mean the task is in progress, just submitted for execution.
service.execute(tasks.get(key).poll());
//Now that we submitted the task for exicution, we can mark the token to false.
tokens.get(key).compareAndSet(true, false);
// But at what point are we going to mark token to True. If this token is updated
// From any other thread than consumer, it will create race condition.
}catch(RejectedExecutionException e){
//What will happen to the polled task. Task is removed from the queue.
//Token we can mark to true, but task has already polled from queue.
// Shall we put it back to the head of the queue and how ?
tokens.get(key).compareAndSet(false, true);
//What if the task is not completed smoothly. T1 is not completed successfully,
// what is backup plan, because next task will start any way and corrupt the file.
}
}
}
}
}
如果我们能把 1. 添加文件、2、修改文件、3. 提交和 4. 推送到远程等任务隔离开来,也许我们可以想出更好的方法。但在这种情况下也会有限制。
最好采用单线程方法。