使用生产者/消费者模型

时间:2018-06-07 15:40:51

标签: java multithreading producer-consumer

在最近删除的帖子中,我提出了以下问题:

我正在尝试编写一个实现Producer / Consumer模型的多线程程序。通常,我想使用一个生产者,它从文件中读取行并将它们放在BlockingQueue中,并让多个消费者在从BlockingQueue检索行并将结果存储在新文件中后进行一些处理。

我希望你能给我一些关于我应该考虑什么以获得高性能的反馈。我花了几周时间阅读有关并发和同步的内容,因为我不想错过任何内容,但我正在寻找一些外部反馈。请在下面找到我需要了解的信息。

  • 我应该使用哪种类型的BlockingQueue实现来获得更好的性能?我无法使用固定大小的BlockingQueue,因为我们不知道文件中有多少行。或者即使生产者被锁定,我应该使用它吗? (如果队列已满)
  • 如果' f()'是生产者用来处理文件行的方法。知道我正在使用BlockingQueue,我应该同步f()吗?如果是,那不会影响我的申请吗?因为其他消费者会等待释放锁。

我希望我没有说错。

您建议在提出问题之前先实施一些内容,因此我删除了该帖子并尝试实施该模型。这是我的代码。

我从一个文件中读取一个线程并将它们放入BlockingQueue的生产者。

class Producer implements Runnable {
    private String location;
    private BlockingQueue<String> blockingQueue;

    private float numline=0;


    protected transient BufferedReader bufferedReader;
    protected transient BufferedWriter bufferedWriter;


    public Producer (String location, BlockingQueue<String> blockingQueue) {
        this.location=location;
        this.blockingQueue=blockingQueue;

        try {
            bufferedReader = new BufferedReader(new FileReader(location));

            // Create the file where the processed lines will be stored
            createCluster();

        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }
    }

    @Override
    public void run() {
        String line=null;
        try {
            while ((line = bufferedReader.readLine()) != null) {
                // Count the read lines
                numline++;
                blockingQueue.put(line);
            }
        } catch (IOException e) {
            System.out.println("Problem reading the log file!");
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public void createCluster () {
        try {
            String clusterName=location+".csv";
            bufferedWriter = new BufferedWriter(new FileWriter(clusterName, true));
            bufferedWriter.write("\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

消费者,其中多个线程将来自BlockingQueue并进行一些处理&#39; f()&#39;并将结果存储在新文件中。

class Consumer implements Runnable {
    private String location;
    private BlockingQueue<String> blockingQueue;

    protected transient BufferedWriter bufferedWriter;

    private String clusterName;

    public Consumer (String location, BlockingQueue<String> blockingQueue) {
        this.blockingQueue=blockingQueue;
        this.location=location;

        clusterName=location+".csv";
    }

    @Override
    public void run() {
        while (true) {
            try {
                //Retrieve the lines
                String line = blockingQueue.take();
                String result = doNormalize (line);
                // TO DO
                //
                //bufferedWriter = new BufferedWriter(new FileWriter(clusterName, true));
                //BufferedWriter.write(result+ "\n");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } 
        }
    }

//Pattern pattern, Matcher matcher
    private String doNormalize(String line){
        String rules [] = getRules(); // return an array of Regex
        String tmp="";

        for (String rule : rules) {
            Pattern pattern = Pattern.compile(rule);
            Matcher matcher = pattern.matcher(line);

            if (matcher.find()){
                Set<String> namedGroups = getNamedGroupCandidates(rule);
                Iterator<String> itr = namedGroups.iterator();
                while(itr.hasNext()){
                    String value=itr.next();
                    tmp=tmp+matcher.group(value)+", ";
                }


        tmp = tmp + "\t";
                    break;
                }
            }
            return tmp;

        }
private Set<String> getNamedGroupCandidates(String regex) {
            Set<String> namedGroups = new TreeSet<String>();
            Matcher m = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>").matcher(regex);
            while (m.find()) {
                namedGroups.add(m.group(1));
            }
            return namedGroups;
        }
}

和我的主类中的代码。使用1个生产者和3个消费者

BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);

            Producer readingThread = new Producer(location, queue);
            new Thread(readingThread).start();

            Consumer normalizers = new Consumer(location,queue);
            ExecutorService executor = Executors.newFixedThreadPool(3);
            for (int i = 1; i <= 3; i++) {
                executor.submit(normalizers);
            }
            System.out.println("Stopped");
            executor.shutdown();

我知道我的代码是不完整的,因为我需要关闭并刷新读取器和写入等等。但是你能告诉我到目前为止我在实现生产者/消费者模型时所犯的错误吗?而且在方法f()上,它是一个处理一条线并产生结果的方法,我不认为我应该同步它,因为我希望所有消费者同时使用。

修改

最后,这个post让我很困惑,它表明如果消费者将结果存储在文件中,它将减慢这个过程。这可能是一个问题,因为我想要性能和速度。

贝斯茨,

1 个答案:

答案 0 :(得分:0)

对于我的第二个问题:&#34; SingleConsumer to&#34; know&#34;多个消费者已经完成消费/处理所有线路。&#34;。我从这个post中得到了启发,结合了这个评论:每个消费者应该发送一个&#34;我终止&#34;消息到队列2,如果单个输出消费者收到所有这些消息,它也可以终止。

所以,对消费者而言;这是我在run()方法中写的内容:

@Override
public void run() {
// A Consumer keeps taking elements from the queue 1, as long as the Producer is
// producing and as long as queue 1 is not empty.
    while (true) {
        try {

            //Retrieve the lines
            String line = firstBlockingQueue.take(); 
If a special terminating value is found.
            if (line==POISON_PILL) {
// The consumer notifies other consumers and the SignleConsumer that operates on queue 2
// and then terminates.
                firstBlockingQueue.put(POISON_PILL);
                secondBlockingQueue.put(SINGLE_POISIN_PILL);
                return;
            }
            // Put the normalized events on the new Queue
            String result = doNormalize (line);
            if (result!=null) {
                secondBlockingQueue.put(result);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } 
    }
} 

至于SinglerConsumer,它应该计算&#34;我已完成处理&#34;消费者发送的消息或我将其用作SINGLE_POISON_PILL。当该计数器达到队列1中的消费者数量时终止。

while (true) {
    try {
        //Retrieve the lines
        String line = secondBlockingQueue.take();
        if (line==SINGLE_POISIN_PILL) {

            setCounter(getCounter()+1);
            if (getCounter()== threadNumber) {
                System.out.println("All "+getCounter()+" threads have finished.  \n Stopping..");
                return;
            }
        }

        try {
            if (line != SINGLE_POISIN_PILL) {
                System.out.println(line);
                bufferedWriter.write(line+"\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } 
}

对于我的第二个问题,显然我只需要添加:

        if (line==SINGLE_POISIN_PILL) {
            setCounter(getCounter()+1);
            if (getCounter()== threadNumber) {
                System.out.println("All "+getCounter()+" threads have finished.  \n Stopping..");
                try {
         if (bufferedWriter != null) 
         {
             bufferedWriter.flush();
             bufferedWriter.close();
         }
     } catch (IOException e) {
         e.printStackTrace();
     }
                return;
            }
        }

一旦我刷新并关闭缓冲区,缓冲区开始写入。

希望得到您的反馈。