标签 Golang 下的文章

Golang-channel通道初识

1.通道可以被认为是Goroutines通信的管道;当多个goroutines想共享数据的时候,虽然也提供了传统的同步机制(同步等待组、同步锁),但是Go语言强烈建议的是使用Channel通道来实现goroutine之间的通信;

2.通道的声明和定义一个变量的语法一样; 通道是引用型数据

//声明通道
var 通道名 chan 数据类型
//创建通道:如果通道为nil(就是不存在),就需要先创建通道
通道名 = make(chan 数据类型)

func main(){
    var a chan int
    if a == nil {
        fmt.Println("channel 是nil的,不能使用,需要先创建通道。")
    }
    a = make(chan int)
    fmt.Printf("数据类型:%T", a)

}

//也可以简短声明:
a := make(chan int)

3.通道的注意点

  • 用于goroutine,传递消息
  • 每个通道都有相关联的数据类型
  • 使用通道传递数据 <-
  • chan <-data,发送数据到通道。向通道中写数据
  • data <-chan,从通道中获取数据。从通道中读取数据
  • 阻塞:发送数据 chan <- data,阻塞的,直到另一条goroutine,读取数据来解除阻塞;读取数据,data <- chan,也是阻塞的。直到另一条goroutine,写出数据解除阻塞;
  • 本身channel就是同步的,意味着同一时间,只能有一条goroutine来操作
func main() {
    var ch1 chan bool
    ch1 = make(chan bool)

    go func() {
        for i:=0; i<10; i++ {
            fmt.Println("子goroutine中,i:", i)
        }
        //循环结束,向通道中写数据,表示要结束了。。。
        ch1 <- true
        fmt.Println("结束")
    }()


    //如果主进程先抢占资源,会去chan中读数据,如果没有数据,会阻塞下去..
    data := <- ch1
    fmt.Println("main...data-->:", data)
    fmt.Println("main主进程结束...")
}
//阻塞式通道
func main() {
    ch1 := make(chan int)

    go func() {
        fmt.Println("子goroutine开始执行")

        //time.Sleep(2*time.Second)
        data := <- ch1 //从ch1中读取数据
        fmt.Println("data:", data)
    }()

    //time.Sleep(5*time.Second)
    ch1 <- 10
    fmt.Println("main...over...")
}
//死锁:使用通道的时候要考虑的一个重要因素;只有发送数据,没有接受数据;或者只有接受数据,没有发送数据

func main()  {
    ch1 := make(chan int)
    ch1 <- 5
}
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()

3.关闭通道和通道上范围循环

· 关闭通道,发送者可以关闭通道,来通知接受方不会有更多的数据发送到channel上: close(ch);接受者可以在接收来自通道的数据时使用额外的变量来检查通道是否已经关闭:v,ok := <-ch, 如果ok的值是true,表示成功的读取了一个数据value。如果为false,意味着正在从一个封闭的通道读取数据,从封闭通道读取的值为零值(根据ch的类型的零值)

关闭通道:close(ch)
    子goroutine:写出10个数据
        每写一个,阻塞一次,主goroutine读取一次,解除阻塞

    主goroutine,读取数据
        每次读取数据,阻塞一次,子goroutine,写出一个,解除阻塞

func main() {
    
    ch1 := make(chan int)
    go sendData(ch1)

    for{
        time.Sleep(1*time.Second)
        v, ok := <- ch1
        if !ok {
            fmt.Println("已经读取了所有的数据。。。", ok)
            break
        }
        fmt.Println("读取的数据:", v, ok)
    }
    fmt.Println("main..over...")

}

func sendData(ch1 chan int)  {
    //发送方:10条数据
    for i:=0; i <10; i++ {
        ch1 <- i
    }
    close(ch1)
}

读取的数据: 0 true
读取的数据: 1 true
读取的数据: 2 true
读取的数据: 3 true
读取的数据: 4 true
读取的数据: 5 true
读取的数据: 6 true
读取的数据: 7 true
读取的数据: 8 true
读取的数据: 9 true
已经读取了所有的数据。。。 false
main..over...
  • 通道上的范围循环
func main() {

    ch1 := make(chan int)
    go sendData(ch1)

    //for循环的 for range形式可用于从通道接收值,直到它关闭为止,不需要单独判断
    for v := range ch1{//从通道读取数据,如果没值,则阻塞
        fmt.Println("读取数据:", v)
    }
    fmt.Println("main..over.....")

}

func sendData(ch1 chan int)  {
    //发送方:10条数据
    for i:=0; i <10; i++ {
        time.Sleep(1*time.Second)
        ch1 <- i
    }
    close(ch1)//通知对方,通道关闭;否则会发生死锁
}

Golang进阶-断点续传

1. 主要借助io包里的Seeker接口

type Seeker interface {
    Seek(offset int64, whence int) (int64, error)

    //设置指针光标的位置:
        第一个参数:偏移量,
        第二个参数:如何设置:
            const (
            SeekStart   = 0 // 相对于光标开头
            SeekCurrent = 1 // 相对于光标当前
            SeekEnd     = 2 // 相对于光标结尾
            )
}

fileName := "E:/GoPath/src/a/aa.txt"
    //打开文件,注意是否有此文件

    //file, err := os.Open(fileName)
    file, err := os.OpenFile(fileName, os.O_RDWR, os.ModePerm)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    bs := []byte{0}
    file.Read(bs)
    fmt.Println(string(bs))

    file.Seek(4, io.SeekStart)
    file.Read(bs)
    fmt.Println(string(bs))

    file.Seek(4, io.SeekCurrent)
    file.Read(bs)
    fmt.Println(string(bs))

    file.Seek(0, io.SeekEnd)
    file.WriteString("ABC")

2. 断点续传应用场景(文件的传递:文件的复制)

  • 文件比较大,如何缩短耗时?
  • 如果文件传递过程中,程序被迫中断,再次重启是否需要重头开始?
  • 传递过程中,是否支持暂停和恢复?

3. 原理:通过一个临时文件记录已传递文件的大小,字节的位置; 思路:边复制,边记录复制的总量

func main() {

    srcFile := "E:/GoPath/src/a/1.png"
    //获取文件名
    destFile := srcFile[strings.LastIndex(srcFile, "/")+1: ]
    fmt.Println(destFile)
    temFile := destFile + "temp.txt"
    fmt.Println(temFile)

    file1,err := os.Open(srcFile)
    HandleErr(err)
    file2,err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
    HandleErr(err)
    file3,err := os.OpenFile(temFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
    HandleErr(err)

    defer file1.Close()
    defer file2.Close()

    //1: 先读取临时文件的数据,再seek
    file3.Seek(0, io.SeekStart)//从0开始读
    bs := make([]byte, 100,100)//一个文件的长度
    n1,err := file3.Read(bs)
    //HandleErr(err)
    countStr := string(bs[:n1])
    count,err := strconv.ParseInt(countStr, 10, 64)
    //HandleErr(err)
    fmt.Println(count)

    //2:设置偏移量;设置读,写的位置
    file1.Seek(count, io.SeekStart)
    file2.Seek(count, io.SeekStart)
    data := make([]byte, 1024, 1024)//用来复制文件
    n2 := -1 //读取的数据量
    n3 := -1 //写出的数据量
    total := int(count) //读取的总量

    //3: 复制文件
    for{
        n2,err = file1.Read(data)
        if err == io.EOF || n2 == 0 {
            fmt.Println("复制完毕", total)
            file3.Close()
            os.Remove(temFile)
            break
        }

        n3,err = file2.Write(data[:n2])
        total += n3

        //将复制的总量,存储到临时文件中
        file3.Seek(0, io.SeekStart)
        file3.WriteString(strconv.Itoa(total))

        fmt.Print("total:%d\n", total)


        //假装断电
        if total > 8000{
            //panic("假装断电看看")
        }
    }




}

func HandleErr(err error){
    if err != nil {
        log.Fatal(err)
    }
}

Golang错误机制初识

Go语言-defer语句

  • 位置:仅能在函数和方法中
  • 构成:关键字和一个表达式语句(表达式不能是内建函数和unsafe中的函数)
  • 作用:最后执行;保护措施;节约资源
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    return ioutil.ReadAll(file)
}
  • 注意:当有多个defer时,执行顺序为倒序
func deferIt() {
    defer func() {
        fmt.Print(1)
    }()
    defer func() {
        fmt.Print(2)
    }()
    defer func() {
        fmt.Print(3)
    }()
    fmt.Print(4)
}

输出:4321
  • 特别提示1:有参数传入,比如:fmt.Print(i + 1)。如果代表传入参数的是一个表达式,那么在defer语句被执行的时候该表达式就会被求值了
func deferIt3() {
    f := func(i int) int {
        fmt.Printf("%d ",i)
        return i * 10
    }
    for i := 1; i < 5; i++ {
        defer fmt.Printf("%d ", f(i))
    }
}
打印出1 2 3 4 40 30 20 10
  • 特别提示2:defer携带的表达式语句代表的是对匿名函数的调用(注意外部变量传递)
func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func() {
            fmt.Print(i)
        }()
    }
}
打印出5555

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func(n int) {
            fmt.Print(n)
        }(i)
    }
}
打印出1234

Go语言异常处理——error

  • 处理错误的惯用法之一:Go函数可以一次返回多个结果(怎样在传递错误)
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    return ioutil.ReadAll(file)
}
函数readFile有两个结果声明。第二个结果声明的类型是error。error是Go语言内置的一个接口类型
type error interface { 
    Error() string
}
  • 怎样创造错误:调用标准库代码包errors的New函数即可
if path == "" {
    return nil, errors.New("The parameter is invalid!")
}

Go语言异常处理——panic

  • 可被意译为运行时恐慌,只有在程序运行的时候才会被“抛出来” == 程序崩溃
  • 恐慌是会被扩散的,它会被迅速地向调用栈的上层传递。如果我们不显式地处理它的话,程序的运行瞬间就会被终止。
  • 人为地产生一个运行时恐慌,可以被恢复的;内建函数recover
  • 内建函数panic和recover是天生的一对。前者用于产生运行时恐慌,而后者用于“恢复”它
  • recover函数必须要在defer语句中调用才有效,因为一旦有运行时恐慌发生,当前函数以及在调用栈上的所有代码都是失去对流程的控制权
  • 只有defer语句携带的函数中的代码才可能在运行时恐慌迅速向调用栈上层蔓延时“拦截到”它
defer func() {
    if p := recover(); p != nil {
        fmt.Printf("Fatal error: %s\n", p)
    }
}()
调用了recover函数。该函数会返回一个interface{}类型的值;interface{}代表空接口。Go语言中的任何类型都是它的实现类型。