go语言并发多文件查找重复行

前言:很久没写手记,不是不想写,而是不知道写什么。虽然这段时间进步很快,也学到了很多东西,但是一下笔就发现将要分享的内容没有什么新意,完全是炒冷饭,也就放弃了。不过太久没写,总觉得生活中缺少了一些东西,会有种学了一堆没用知识的错觉,因此偶尔分享下,也算对自己学习的鞭策。

今天分享的内容是go语言并发操作文件的代码。经过这段时间的学习,个人觉得go语言优雅轻量,有很多思想和设计是javascript中所欠缺的。底层设计特别是并发机制基本上是javascript完全缺乏的,因此本篇分享这个内容,以下是正文:

package main

import (
   "fmt"
   "runtime"
   "path"
   "os"
   "bufio"
   "strings"
   "sync"
)
//将文件相对路径转化为绝对路径
func getFileAbsPath3(relativePath ... string) []string {
   var ret = []string{}
   _, file, _, _ := runtime.Caller(1)
   for _, dir := range relativePath {
      var filePath = path.Join(path.Dir(file), dir)
      ret = append(ret, filePath)
   }
   return ret
}

func filterRepeat3()  {
   var absPaths =  getFileAbsPath3("./2.txt", "./3.txt", "./4.txt")

   //设置一个并发控制wgp
   wgp := sync.WaitGroup{}
   //设置一个存储map
   var valuesMap map[string]int = map[string]int{}
   for _, dir := range absPaths {
      wgp.Add(1)
      //并发打开多个文件并读取文件内容
      go func(filePath string) {
         fmt.Println("并发打开文件的顺序为: ", filePath)
         file, error := os.Open(filePath)
         //此处一定要记住close,否则可能会导致文件描述符用完
         defer file.Close()
         if (error != nil) {
            fmt.Println("error: ", error)
         }
         //此处建议使用scanner,如果使用NewReader还需要处理空行和只有一行的情况,不推荐
         scanner := bufio.NewScanner(file)
         //逐行扫描,知道直接或出错
         for scanner.Scan() {
            str := scanner.Text()
            str = strings.TrimSpace(str)
            valuesMap[str] += 1
         }
         wgp.Add(-1)
      }(dir)
   }
   //等待并发程序全部结束
   wgp.Wait()
   for key, value := range valuesMap {
      if value > 1 {
         fmt.Println("找到重复行,重复的行值为:", key, "重复次数为:", value)
      }
   }
   fmt.Println(valuesMap)
}
func main() {
   filterRepeat3()
}

这个版本看上去很清晰,代码也比较简单,但是这是一个有问题的代码,问题就出在go语言是并发变成的语言,可能会报错go fatal error: concurrent map writes(同时写或者读的时候在写)。如何处理呢?两种方式,一种是采用go语言锁,sync.mutex。一种是采用通道,串行写入。这里我采用锁的方式,将读和写都锁定,这样就避免了并发导致的读写冲突问题,代码如下:

package main

import (
   "bufio"
   "fmt"
   "os"
   "path"
   "runtime"
   "strings"
   "sync"
)

//将文件相对路径转化为绝对路径
func getFileAbsPath3(relativePath ...string) []string {
   var ret = []string{}
   _, file, _, _ := runtime.Caller(1)
   for _, dir := range relativePath {
      var filePath = path.Join(path.Dir(file), dir)
      ret = append(ret, filePath)
   }
   return ret
}

var valuesMap = map[string]int{}
var mutex sync.Mutex

//加上读写锁,否则会出现同时读写的冲突
func write(k string) {
   mutex.Lock()
   defer mutex.Unlock()
   valuesMap[k] += 1
}
func filterRepeat3() {
   var absPaths = getFileAbsPath3("./2.txt", "./3.txt", "./4.txt")

   //设置一个并发控制wgp
   wgp := sync.WaitGroup{}
   //设置一个存储map

   for _, dir := range absPaths {
      wgp.Add(1)
      //并发打开多个文件并读取文件内容
      go func(filePath string) {
         fmt.Println("并发打开文件的顺序为: ", filePath)
         file, error := os.Open(filePath)
         //此处一定要记住close,否则可能会导致文件描述符用完
         defer file.Close()
         if error != nil {
            fmt.Println("error: ", error)
         }
         //此处建议使用scanner,如果使用NewReader还需要处理空行和只有一行的情况,不推荐
         scanner := bufio.NewScanner(file)
         //逐行扫描,知道直接或出错
         for scanner.Scan() {
            str := scanner.Text()
            str = strings.TrimSpace(str)
            write(str)
         }
         wgp.Add(-1)
      }(dir)
   }
   //等待并发程序全部结束
   wgp.Wait()
   for key, value := range valuesMap {
      if value > 1 {
         fmt.Println("找到重复行,重复的行值为:", key, "重复次数为:", value)
      }
   }
   fmt.Println(valuesMap)
}
func main() {
   for i := 0; i < 5000; i++ {
      filterRepeat3()
   }

}

代码并不复杂,有些稍微需要描述的地方都有代码注释。另外最后推荐两本go语言书籍,特别值得一看:《go并发编程实战第二版》《go程序设计语言》

thx~

点赞