• Golang实现超时机制读取文件


    读写文件是Go程序的基本任务,包括使用程序查看文件内容、创建或修改文件。Go提供了os,ioutil,io以及bufio包实现文件操作。本文介绍如果在读文件过程中增加超时机制,避免文件太大一直占用资源。

    协程与通道

    协程(Goroutine)是轻量级线程,可实现函数或方法与主程序流并行执行。使用go关键字:go func(){}。通道是协程直接的通讯管道,主要用于在协程间传输数据,即往通道写数据、从通道读数据。

    通过chan关键字声明通道,可以使用var或:=两种方式声明,也可以声明待缓存的通道,语法如下:

    channelName:= make(chan Type, n)
    
    • 1

    举例:

    dataStream := make(chan string, 1)
    
    • 1

    往通道写数据:

    dataStream <- "data"
    
    • 1

    从通道读数据:

    varName := <-dataStream
    
    • 1

    关闭通道:

    close(dataStream)
    
    • 1

    Go 超时机制

    超时对于连接到外部资源或需要限制执行时间的场景来说非常重要。这是因为太长的服务器端处理将消耗太多的资源,导致并发性下降,甚至服务不可用。

    利用select语句及并行协程实现超时,必须导入time包。然后使用time.After()参数创建通道,调用time.After(1 * time.Second)将在1秒后填充通道。下面示例通过通道和select实现超时:

    package main
    import (
        "fmt"
        "time"
    )
    func main() {
        dataChannel:= make(chan string, 1)
    
        go func() {
            time.Sleep(2 * time.Second)
            dataChannel <- "result 1"
        }()
    
        select {
            case results := <- dataChannel:
                fmt.Println(results)
            case <-time.After(1 * time.Second):
                fmt.Println("timeout 1")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    首先创建缓存通道dataChannel,调用函数模拟复杂业务,2秒后从非阻塞通道返回结果。select语句实现超时。results := <- dataChannel等待结果,time.After(1 * time.Second)语句1秒后返回值,因此select首先等待1秒,超过1秒将超时。

    下面利用该机制实现读取文件超时机制实现。

    读取整个文件

    Go中读整个文件一般使用ioutil/os包中的Read File()函数,读取整个文件值字节slice。ioutil包最好别用于读取大文件,对于小文件完全够用。

    os包包含执行参数数组Args,包括执行命令的所有参数,为字符串类型数组。

    package main
    
    import (
    	"fmt"
    	"io/ioutil"
    	"os"
    	"strconv"
    	"time"
    )
    
    func main() {
    
    	filePath := os.Args[1]
    	timeOut, _ := strconv.ParseFloat(os.Args[2], 64)
    
    	// buffered channel of dataStream
    	dataStream := make(chan string, 1)
    	// Run ReadFileToString function in it's own goroutine and pass back it's
    	// response into dataStream channel.
    	go func() {
    		data, _ := ReadFileToString(filePath)
    		dataStream <- data
    		close(dataStream)
    	}()
    
    	// Listen on dataStream channel AND a timeout channel - which ever happens first.
    	select {
    	case res := <-dataStream:
    		fmt.Println(res)
    	case <-time.After(time.Duration(timeOut) * time.Second):
    		fmt.Println("Program execution out of time ")
    	}
    }
    func ReadFileToString(file string) (string, error) {
    	content, err := ioutil.ReadFile(file)
    	// error encountered during reading the data
    	if err != nil {
    		return "", err
    	}
    	// convert bytes to string
    	return string(content), nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    我们可以使用不同的超时时间进行测试:

    go run main.go text.txt 1.0
    
    go run main.go text.txt 0.9
    
    • 1
    • 2
    • 3

    按行读文件

    可以使用 bufio.Scanner 按行读文件,使用bufio.NewScanner(file)构造函数创建Scanner,然后通过Scan()和Text()方法逐行读取内容。使用Err()方法检查读取文件过程中的错误。

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"log"
    	"os"
    	"strconv"
    	"time"
    )
    
    func main() {
    	//get filepath and timeout on the terminal
    	filePath := os.Args[1]
    	timeOut, _ := strconv.ParseFloat(os.Args[2], 64)
    	//creating  channels
    	dataStream := make(chan string, 1)
    	readerr := make(chan error)
    
    	// Run ReadFileLineByLine function in its own goroutine and pass back it's
    	// response into dataStream channel.
    	go ReadFileLineByLine(filePath, dataStream, readerr)
    
    loop:
    	for {
    		// select statement will block this thread until one of the three conditions below is met
    
    		select {
    		case data := <-dataStream:
    			// Process each line
    			fmt.Println(data)
    		case <-time.After(time.Duration(timeOut) * time.Second):
    			fmt.Println("Program execution out of time ")
    			break loop
    		case err := <-readerr:
    			if err != nil {
    				log.Fatal(err)
    			}
    			break loop
    		}
    	}
    }
    
    func ReadFileLineByLine(filePath string, data chan string, er chan error) {
    	// open file
    	file, err := os.Open(filePath)
    	if err != nil {
    		fmt.Println(err)
    	}
    	// close the file at the end of the program
    	defer file.Close()
    
    	// read the file line by line using scanner
    	scanner := bufio.NewScanner(file)
    
    	for scanner.Scan() {
    		data <- scanner.Text()
    	}
    	close(data) // close causes the range on the channel to break out of the loop
    	er <- scanner.Err()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    当然也可以使用不同超时时间进行测试,如超时场景:

    go run main.go test.txt 0.1
    # Program execution out of time
    
    • 1
    • 2

    按块方式读文件

    对于非常大的文件使用块方式读取非常有用,无需把整个文件加载到内存中,每次读取固定块大小内容。下面readFileChunk函数中需要创建缓冲区,每次读取该缓冲区大小的内容,直到io.EOF错误出现,表明已经到达文件结尾。

    缓冲区大小、目标文件以及超时时间作为函数参数,其他逻辑与上面示例一致:

    package main
    
    import (
       "fmt"
       "io"
       "log"
       "os"
       "strconv"
       "time"
    )
    
    func main() {
       dataStream := make(chan string, 1)
       filePath := os.Args[1]
       timeOut, _ := strconv.ParseFloat(os.Args[2], 64)
       chunkSize, _ := strconv.Atoi(os.Args[3])
       go readFileChunk (filePath, dataStream, int64(chunkSize))
       select {
       case resultData := <- dataStream:
          fmt.Println(resultData)
       case <-time.After(time.Duration(timeOut) * time.Millisecond):
          fmt.Println("timeout")
       }
    
    }
    
    func readFileChunk(filePath string, data chan string, chunkSize int64) {
       // open file
       f, err := os.Open(filePath)
       if err != nil {
          log.Fatal(err)
       }
       // remember to close the file at the end of the program
       defer f.Close()
    
       buf := make([]byte, chunkSize)
       for {
          readTotal, err := f.Read(buf)
          if err != nil && err != io.EOF {
             log.Fatal(err)
          }
    
          if err == io.EOF {
             break
          }
    
          data <- string(buf[:readTotal])
       }
       close(data)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    总结

    对于资源密集型程序正在执行时,Golang超时机制是必要的,它有助于终止这种冗长的程序。本文通过示例展示了不同方式读取文件的超时机制实现。

  • 相关阅读:
    randlanet 分割点云单文件运行 输入PCD或 kitti bin激光数据(附完整单个运行代码)
    Linux - 输入输出
    IDEA中java工程 maven编译后中代码仍爆红
    k8s核心概念Controller进阶之DaemonSet、Job、CronJob
    Django面试题和出现的一些问题
    C/C++ 通过域名获取服务器真实IP地址
    null 不好,我真的推荐你使用 Optional
    vue路由切换时,若是一个路径但是参数不同,页面不会变化
    [Hot100]回文子串 与 最长回文子串
    2023-10-9 腾讯云智校招后端开发二面(附答案)
  • 原文地址:https://blog.csdn.net/neweastsun/article/details/127549755