协程(Goroutine)是轻量级线程,可实现函数或方法与主程序流并行执行。使用go关键字:go func(){}。通道是协程直接的通讯管道,主要用于在协程间传输数据,即往通道写数据、从通道读数据。
通过chan关键字声明通道,可以使用var或:=两种方式声明,也可以声明待缓存的通道,语法如下:
1 |
channelName:= make(chan Type, n) |
举例:
1 |
dataStream := make(chan string, 1) |
往通道写数据:
1 |
dataStream <- "data" |
从通道读数据:
1 |
varName := <-dataStream |
关闭通道:
1 |
close(dataStream) |
超时对于连接到外部资源或需要限制执行时间的场景来说非常重要。这是因为太长的服务器端处理将消耗太多的资源,导致并发性下降,甚至服务不可用。
利用select语句及并行协程实现超时,必须导入time包。然后使用time.After()参数创建通道,调用time.After(1 * time.Second)将在1秒后填充通道。下面示例通过通道和select实现超时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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") } } |
首先创建缓存通道dataChannel,调用函数模拟复杂业务,2秒后从非阻塞通道返回结果。select语句实现超时。results := <- dataChannel等待结果,time.After(1 * time.Second)语句1秒后返回值,因此select首先等待1秒,超过1秒将超时。
下面利用该机制实现读取文件超时机制实现。
Go中读整个文件一般使用ioutil/os包中的Read File()函数,读取整个文件值字节slice。ioutil包最好别用于读取大文件,对于小文件完全够用。
os包包含执行参数数组Args,包括执行命令的所有参数,为字符串类型数组。
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 |
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 |
go run main.go text.txt 1.0
go run main.go text.txt 0.9 |
可以使用 bufio.Scanner 按行读文件,使用bufio.NewScanner(file)构造函数创建Scanner,然后通过Scan()和Text()方法逐行读取内容。使用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 |
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 |
go run main.go test.txt 0.1 # Program execution out of time |
对于非常大的文件使用块方式读取非常有用,无需把整个文件加载到内存中,每次读取固定块大小内容。下面readFileChunk函数中需要创建缓冲区,每次读取该缓冲区大小的内容,直到io.EOF错误出现,表明已经到达文件结尾。
缓冲区大小、目标文件以及超时时间作为函数参数,其他逻辑与上面示例一致:
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 |
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) } |
对于资源密集型程序正在执行时,Golang超时机制是必要的,它有助于终止这种冗长的程序。本文通过示例展示了不同方式读取文件的超时机制实现。