【问题标题】:Applying parallelism to counting word occurrences in a file应用并行计算文件中的单词出现次数
【发布时间】:2020-04-17 17:50:11
【问题描述】:

我制作了一个程序来计算文件中目标字符串的出现次数。它应该使用并行性来完成此任务,但我似乎无法弄清楚如何编写 run() 来仅评估文件的一部分,以便它的不同线程可以评估文件的其余部分。至少,这是我对并行的理解。我已经在文档中观看了几天的视频,真的只需要有人向我解释一下;不是如何逐步解决我的特定问题本身,而是解释多线程,而不是使用带有打印线程 id 的循环的主方法。我知道我的类需要实现 Runnable 并且 run() 需要被覆盖。我不确定应该如何编写 run() 以仅在无法传递参数时处理文件的一部分。


    public static void main(String[] args) {
        new Thread(new Test()).start();
        new Thread(new Test()).start();
        System.out.println("My program counts: " + Test.getTotal() + " occurences of 'the'.");
    }
}
public class Test implements Runnable {

    private File alice = new File(getCurrentDir() + "/alice.txt");
    private String[] words;
    private BufferedReader reader;
    private StringBuilder sb;
    private int count;
    private static int total;

    public void run() {
        getAlice();
        for(int i = 0; i < words.length; i++) {
            if(words[i].toLowerCase().equals("the")) {
                count++;
            }
        }
        total = count;
    }
    public void getAlice() {
        try{
            reader = new BufferedReader(new FileReader(alice));
            sb = new StringBuilder();
            String line = "";
            while((line = reader.readLine()) != null) {
                sb.append(line);
            }
            words = sb.toString().split(" ");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public String getCurrentDir() {
        String currDir = System.getProperty("user.dir");
        return currDir;
    }
    public String[] getWords() {
        return words;
    }
    static int getTotal() {
        return total;
    }
}```

【问题讨论】:

  • Files.lines(Path.of(System.getProperty("user.dir"), "alice.txt")).parallel().mapToInt(l -&gt; l.split(" ").count).sum();

标签: java multithreading


【解决方案1】:

如果没有办法干净利落地分割文件,这将很难做到:任意分割文件可能会分割单词。

如果文件被分成几行,并且如果行从不分割单词,那我们就有了工作的余地。

一种设计会有一个阅读器线程和一个字数统计线程池。

读取线程将获得一个停止的计数线程,从该线程获取读取缓冲区,将下一行读入缓冲区,然后恢复计数线程。

计数线程将逐步遍历其读取缓冲区,该缓冲区将保存单行文本,并通过将行上的字数添加到全局字数总数来完成。完成后,计数线程会将自己放回可用线程池中。

这是否是性能提升将取决于执行 IO 所花费的相对时间与计算单词所花费的时间相比。计算字数可能比 IO 快得多,以至于并行性不会加快处理速度,甚至可能由于线程管理开销而减慢处理速度。

或者,如果文件已经被读取并分成几行,这意味着 IO 不包括在性能中,那么可能会获得性能提升。

此外,线程数以及计数线程占用一行还是多行可能也很重要。

【讨论】:

    【解决方案2】:

    当我无法传递参数时,我应该如何编写 run() 只处理文件的一部分?

    您可以传递参数,但您将它们传递给 Test 构造函数,然后将它们保存在字段中,以供 run() 方法使用。

    public class Test implements Runnable {
        private final int partToProcess;
    
        public Test(int partToProcess) {
            this.partToProcess = partToProcess;
        }
    
        @Override
        public void run() {
            // use this.partToProcess here
        }
    }
    

    在线程处理完文件之前,您不应调用getTotal()

    要等待线程结束,请调用join()

    您也不应该使用static 作为总数。

    // Create threads
    Test test0 = new Test(0);
    Test test1 = new Test(1);
    Thread thread0 = new Thread(test0);
    Thread thread1 = new Thread(test1);
    
    // Start threads
    thread0.start();
    thread1.start();
    
    // Wait for threads to end
    thread0.join();
    thread1.join();
    
    // Now we can print result here
    int total = test0.getTotal() + test1.getTotal();
    System.out.println("My program counts " + total + " occurrences of 'the'.");
    

    如果您想将文件分成两份以上,您应该使用数组来存储TestThread 实例。


    要读取文件的后半部分,您不能使用FileReader

    参见例如提问“How to read a file from a certain offset in Java?”以了解更多信息。

    请注意,同时从文件中的 2 个不同位置读取会减慢处理速度,除非您使用的是 SSD,因为普通硬盘臂不能同时位于两个位置。作为多线程练习,这很好,但实际上,您可能不想这样做。

    另请注意,当您按文件大小将文件一分为二时,您可能会在单词中间分割文件的文本,并且如果文本文件使用像 UTF-8 这样的多字节编码,您甚至可能会拆分字符的字节,因此您需要添加代码来检测并解决它。

    【讨论】:

      【解决方案3】:

      基本上,您使用 Threads 做得很好,但在输出时您必须从实例中获取值。

      Test t1  = new Test();
      Thread th = new Thread(t1);
      th.start();
      //wait till Thread_th finish run method
      while(th.getState() != Thread.State.TERMINATED)
      {
         //Thread-states
         //New, Runnable, Blocked, Waiting, Timed Waiting, Terminated
      }
      System.out.println(t1.getTotal());
      

      更多private static int total 不是静态的!
      更简单的例子

      public class MyT implements Runnable {
      
      double d;
      public static void main(String[] args)
      {
          MyT myt = new MyT();
          Thread t1 = new Thread(myt);
          t1.start();
          while(t1.getState() != Thread.State.TERMINATED)
          {
              System.out.println(t1.getState());
          }
          System.out.println(t1.getState()+"_"+myt.getD());
      }
      
      @Override
      public void run() {
      
          for(int i=0;i<3;i++)
          {
              d = Math.random();
              System.out.println(d);
          }
      }
      public double getD()
      {
          return d;
      }
      

      输出

      NEW
      ...
      RUNNABLE
      ...
      BLOCKED
      0.7175015787267744
      0.6915288485156048
      0.777565206934673
      RUNNABLE
      ...
      TERMINATED_0.777565206934673
      

      【讨论】:

      • 我不太确定 while 循环中的 cmets 是什么意思
      • 您必须查看 Thread 文档。一个线程有一些状态(作为有限自动化工作)。当start Thread 你必须等到run method 完成。然后你可以得到计算。查看输出。我只有在运行完成后才得到 D,这意味着 T.state 已终止。
      • Thead 没有立即得到结果。需要时间run to finish。所以当Thread 完成时检查while (state is TERMINATED)
      • t1.join 的作用与while(t1.getState() != Thread.State.TERMINATED) 相同:Wait_for_run_to_finish。 join 是首选,但两者都可以使用相同的结果。
      猜你喜欢
      • 1970-01-01
      • 2012-08-09
      • 1970-01-01
      • 2023-04-04
      • 2018-01-02
      • 1970-01-01
      • 2013-06-29
      • 1970-01-01
      相关资源
      最近更新 更多