Go-指针&通道

背景

本篇主要介绍 Go 语言的指针&通道的用法。

相关资料

指针

指针变量

func basicPtr() {
    a := 10
    fmt.Printf("变量a的内存地址十六进制:%p\n", &a)
 
    var ptr *int
    fmt.Println("空的指针变量:", ptr)
    fmt.Printf("空的指针变量内存地址十六进制: %p\n", ptr)
    if ptr == nil {
        fmt.Println("ptr is nil")
    }
 
    // 将变量a的内存地址赋值给指针变量ptr
    ptr = &a
    fmt.Println("ptr:", ptr)
    fmt.Println("*ptr:", *ptr)
 
    var pptr **int
    pptr = &ptr
    fmt.Println("pptr:", pptr)
    fmt.Println("*pptr:", *pptr)
    fmt.Println("**pptr:", **pptr)
}

知识点:

  • 将 & 放到一个变量前,就会返回变量的内存地址
  • 将 * 放到一个变量类型前,表示变量为一个指针变量
  • 当一个指针变量被定义后,没有赋值前,它的值为 nil,内存地址为0
  • 想要获取指针变量指向的值,则在指针变量前加一个 * ,指向指针的指针变量,需要加两个*

指针数组

func arrPtr() {
    arr := []int{2, 4, 6}
    for index, value := range arr {
        fmt.Printf("arr[%d]: %d, %p\n", index, value, &arr[index])
    }
 
    // ptr是指针数组
    var ptrArr [3]*int
    for index := range arr {
        ptrArr[index] = &arr[index]
    }
    fmt.Println("ptrArr:", ptrArr)
 
    for index := range ptrArr {
        fmt.Printf("ptrArr[%d] : %p -> %d\n", index, ptrArr[index], *ptrArr[index])
    }
}

指针作为函数参数

基础版:

func ptrFunc() {
    x := 3
    fmt.Println("x:", x)
    x1 := add(x)
    fmt.Println("x1:", x1)
    fmt.Println("x:", x)
 
    fmt.Println("-----------------------")
 
    y := 3
    fmt.Println("y:", y)
    y1 := addPtr(&y)
    fmt.Println("y1:", y1)
    fmt.Println("y:", y)
}
 
/**
 * 参数为变量的值
 */
func add(a int) int {
    a = a + 1
    return a
}
 
/**
 * 参数为变量的内存地址
 */
func addPtr(a *int) int {
    *a = *a + 1
    return *a
}

知识点:

  • add函数的入参,传递的是变量x的值的copy,当add函数执行时,变量x不会有任何变化
  • addPtr函数的入参,传递的是变量x的内存地址,当addPtr函数执行时,会修改变量x的值

传递指针的好处:

  • 传指针使得多个函数能操作同一个对象
  • 传指针比较轻量级(8bytes),只是传递内存地址,可以用指针传递体积大的结构体
  • channel、slice、map这三种类型实现机制类似指针,所以可以直接传递

测试map参数:

func testMap() {
    myMap := make(map[string]int)
 
    myMap["zhangsan"] = 10
    myMap["lisi"] = 11
    myMap["wangwu"] = 12
 
    fmt.Println("myMap:", myMap)
    fmt.Printf("myMap:%p\n", myMap)
 
    fmt.Println("-----------------------")
 
    myMap2 := modifyMap(myMap)
    fmt.Println("myMap2:", myMap2)
    fmt.Printf("myMap2:%p\n", myMap2)
    fmt.Println("myMap:", myMap)
    fmt.Printf("myMap:%p\n", myMap)
}
 
func modifyMap(myMap map[string]int) map[string]int {
    myMap["lisi"] = 100
    return myMap
}

测试channel传参:

func testChannel() {
    ch := make(chan *int, 1)
    a := 1
    ptr := &a
    modifyChannel(ptr, ch)
 
    ptr2 := <-ch
 
    fmt.Printf("a: %d, %p\n", a, &a)
    fmt.Printf("ptr: %d, %p\n", *ptr, ptr)
    fmt.Printf("ptr2: %d, %p\n", *ptr2, ptr2)
}
 
func modifyChannel(ptr *int, ch chan *int) {
    *ptr = 100
    ch <- ptr
}

 

通道

通道基础

func basic() {
    ch := make(chan int)
    defer close(ch)
 
    go send(1, ch)
    fmt.Println(<-ch)
 
    //fmt.Println(<-ch)
    go receive(ch)
}
 
func send(a int, ch chan int) {
    ch <- a
}

知识点:

  • 操作符 <- 用于指定通道的方向,发送或接收
  • 如果通道不带缓冲:
    • 发送方会阻塞直到接收方从通道中接收了值,所以如果不使用 goroutines 去执行发送,那整个程序就会发生死锁,最终导致panic
    • 当通道中无数据,如果不使用 goroutines 去接收,那整个程序也会发生死锁,最终导致panic

通道关闭

func testClose() {
    ch := make(chan int)
 
    go send(1, ch)
    fmt.Println(<-ch)
    close(ch)
 
    // 判断通道是否被关闭
    v, ok := <-ch
    if ok {
        fmt.Println("channel is open, value:", v)
    } else {
        fmt.Println("channel is closed")
 
        //close(ch)
 
        //go send(2, ch)
        //fmt.Println(<-ch)
    }
}

知识点:

  • 给已关闭的通道发送或者再次关闭都会导致运行时的panic

向通道中多次发送

func testMultiSend() {
    arr := []int{1, 2, 3, 4, 5, 6}
    ch := make(chan int)
 
    for _, value := range arr {
        go add(value, ch)
    }
 
    i := 0
    for i < len(arr) {
        fmt.Println("receive -> ", <-ch)
        i++
    }
}
 
/**
 * 将结果发送到通道ch
 */
func add(a int, ch chan int) {
    fmt.Println("send -> ", a)
    ch <- a
}

知识点:

  • 一个通道相当于是一个先进先出的队列,也就是说通道中的元素都是严格的按照发送的顺序排序的,接收的顺序也完全是按照发送的顺序接收的

无缓冲区实例

func calc() {
    arr := []int{1, 2, 3, 4, 5, 6}
    ch := make(chan int)
 
    arrLen := len(arr) / 2
    go sum(arr[:arrLen], ch)
    go sum(arr[arrLen:], ch)
 
    // 从通道 ch 中接收
    x, y := <-ch, <-ch
 
    fmt.Println(x, y)
}
 
/**
 * 计算数组arr的累计值,并将结果发送到通道ch
 */
func sum(arr []int, ch chan int) {
    sum := 0
    for _, v := range arr {
        sum += v
    }
    fmt.Println("send -> ", sum)
    ch <- sum // 把 sum 发送到通道 ch
}

知识点

  • channel 接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得 Goroutines 同步变的更加的简单,而不需要显式的 lock

缓冲区通道

func testChannelBuffer() {
    ch := make(chan int, 3)
 
    ch <- 1
    ch <- 2
    ch <- 3
    //ch <- 4
 
    fmt.Println("cap(ch):", cap(ch))
    fmt.Println("len(ch):", len(ch))
 
    fmt.Println(<-ch)
    fmt.Println(<-ch)
 
    fmt.Println("len(ch):", len(ch))
 
    ch <- 5
    fmt.Println(<-ch)
}

知识点:

  • 带缓冲区的通道,允许发送端发送的数据放在缓冲区里面,当缓冲区未满时,不会阻塞,当缓冲区满了,发送端就无法再发送数据了
  • cap(ch):查询一个通道的容量(无缓冲区通道返回0)
  • len(ch):查询当前有多少个已被发送到此通道但还未被接收出去的元素(无缓冲区通道返回0)

缓冲区通道实例

var wg sync.WaitGroup
 
/**
 * 测试通道-复杂例子(带缓冲区)
 */
func channelBufferExample() {
    arr := []int{1, 2, 3, 4, 5, 6}
    ch := make(chan int, 10)
 
    wg.Add(len(arr))
 
    for _, value := range arr {
        go addWhiteWg(value, ch)
    }
 
    // 当协程未全部执行完时,一直阻塞(类似于java的CountDownLatch)
    wg.Wait()
 
    close(ch)
 
    for {
        v, ok := <-ch
        if ok {
            fmt.Println("channel is open, receive value:", v)
        } else {
            fmt.Println("channel is closed")
            break
        }
    }
}
 
/**
 * 将结果发送到通道ch
 */
func addWhiteWg(a int, ch chan int) {
    fmt.Println("send -> ", a)
    ch <- a
    wg.Done()
}

知识点:

  • 带缓冲的通道相当于是一个先进先出的队列,也就是说通道中的元素都是严格的按照发送的顺序排序的,接收的顺序也完全是按照发送的顺序接收的
  • 带缓冲的通道关闭后,如果缓冲区有数据,接收方仍然可以从缓冲区中获取数据

测试select

func testSelect() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}
 
func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

 知识点:

  • select 默认是阻塞的,只有当监听的 channel 中有发送或接收可以进行时才会运行,当多个 channel 都准备好的时候, select 是随机的选择一个执行的

 

posted @ 2022-11-02 09:18  仅此而已-远方  阅读(112)  评论(0编辑  收藏  举报