通道是隐式传递引用吗?

83

Go 之旅上有一个关于通道的例子:https://tour.golang.org/concurrency/2

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

在 sum 函数中修改了通道 c,这些更改会在函数终止后继续存在。显然,c 是按引用传递的,但没有创建指向 c 的指针。在 Go 中,通道是否隐式按引用传递?


16
是的,在 Go 语言中,引用类型包括 slice(切片)、map(映射)和 channel(通道)。在传递它们时,你正在复制引用。(虽然字符串是不可变的,但也被实现为引用类型。) - user1106925
4个回答

100
技术上说,它们是复制的,因为使用make时,你正在堆上分配某些内容,所以从技术上讲它是一个指针。但指针类型不会被暴露,所以可以将它们视为引用类型。
编辑:来自规范:
make内置函数接受一种类型T,它必须是切片、映射或通道类型,后面可以选择跟随一种类型特定的表达式列表。它返回类型T的值(而不是*T)。这个内存的初始化如初始化值章节中所述。
通道必须在使用之前初始化。 make() 这样做,所以可以将其用作引用类型。
这基本上意味着您可以将其传递给函数并对其进行写入或读取。经验法则是,如果您使用makenew&,则可以将其传递到另一个函数而无需复制底层数据。
因此,以下是“引用”类型:
- 切片 - 映射 - 通道 - 指针 - 函数
当传递到函数中时,仅数据类型(数字、布尔值和结构等)会被复制。字符串很特殊,因为它们是不可变的,但不会按值传递。这意味着以下内容不会按预期工作:
type A struct {
    b int
}
func f(a A) {
    a.b = 3
}
func main() {
    s := A{}
    f(s)
    println(s.b) // prints 0
}

6
"make"并不意味着堆分配,而"slice"实际上是作为结构体实现的,并且在传递时进行复制。 - zzzz
1
指针并不是真正的引用类型,它们是值类型,只是作为引用来使用。 - Casey Rodarmor
4
mapchan不同,slice不像“不透明指针”那样运作。如果一个函数使用参数a []string并将其更改为a = append(a, x),则调用者将无法看到长度的更改。对于现有索引的更改是可见的(除非在同一函数中的append导致重新分配)。 - karmakaze
那么,明确一下,您是说通道不需要作为指针传递,即foo(&myChan),因为它们不会被修改,也就是说在它们被传递到函数中时不会被作为数据改变。相反,当您通过值传递通道,即foo(myChan)时,您只是复制底层指针的地址并将其传递给函数。这对我来说很合理。 - Subtubes
1
@Subtubes 正确,通道不需要作为指针传递,因为chan类型已经是指向结构体的指针(尝试 c := make(chan int, 0); log.Printf("%v", c))。在调用函数时,该指针被复制并传递给函数。而切片类型由具有(指针、长度、容量)值的结构体组成。在调用函数时,这个(指针、长度和容量)结构体被复制并传递给函数。请参阅Go是按值传递的——但它可能不总是感觉到以获取更详细的解释。 - bain
显示剩余2条评论

17

Go 中的所有内容都通过值传递和分配。某些内置类型,包括通道类型和映射类型,行为类似于指向某些隐藏内部结构的不透明指针。可以通过对通道或映射的操作来修改该内部结构。它们最初是 nil,类似于 nil 指针。


一切都矛盾于所选答案。 - TheRealFakeNews
@TheRealFakeNews:是的,所有的都是。当然,如果您对传值有不同的定义,您可能会持不同意见。我的定义是,如果在被调用函数中对参数变量进行简单赋值对于在调用作用域中传递的变量没有影响,则它是传值方式。这个语义定义与通常接受的说法(在本网站和其他地方)一致,即Java只有传值方式。Go中的传递语义也是相同的。 - newacct

3
您可以这么说,但是说“通道c在sum函数中被修改”并不是正确的术语。通道发送和接收并不被认为是修改。
请注意,切片和映射表现出类似的行为,请参见http://golang.org/doc/effective_go.html以获取更多详细信息。
此外,“按引用传递”意味着可以对sum中的c进行赋值,从而改变它在sum之外的值(而不是其基础数据),这并不是事实。

3

通道变量是引用,但这取决于您对“引用”的定义。 语言规范 从未提到引用类型。

sum函数中没有“修改”任何通道(变量)。向通道发送数据会改变其状态。

换句话说,是的,通道是作为指向某个运行时结构的指针实现的。请注意,这对于引用语义是严格必要的。

编辑:上面的句子应该写成:“请注意,这对于引用语义来说不是严格必要的。”即单词“not”消失了。对于可能造成的任何困惑,我们深感抱歉。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接