如果局部变量在被放入通道后失去其作用域,会发生什么情况?

What happens if a local variable loses its scope after being put into a channel?

提问人:xc wang 提问时间:6/28/2021 最后编辑:Jonathan Hallxc wang 更新时间:6/29/2021 访问量:864

问:

特别是当变量是在本地作用域内生成的时。它的存活时间有多长?

例如,给定一个循环创建 10 只狗并将指针传递到一个通道中,例如

for i := 0; i < 10; i++ {
    dogAddr := produce(i)    // assume we already have: func produce(i int) *Dog 
    c <- dogAddr             // c: channel
}

当循环结束时,狗会立即被释放吗?它们会不会只存活一段神奇的时间等待被吞噬,被吞噬后会被释放吗?

我在一个简单的代码中对此进行了测试,结果似乎表明局部变量将永远存在。

package main

import (
    "fmt"
    "time"
)
func main() {
    var a int
    var c chan *int = make(chan *int, 1000)
    var m map[int]*int = make(map[int]*int)
    for i := 0; i < 10; i++ {                     // this is generation-loop
        x := i
        m[i] = &x
        fmt.Println(i, "mapping to: ", &a)
        c <- &x
    }                                            // the generation-loop breaks here
    for i := 0; i < 10; i++ {
        fmt.Println(i, "stored pointer: ", m[i]) // we can still call the variables 
    }
    for i := 0; i < 10; i++ {
        fmt.Println(i, "stored value: ", *m[i])  // we can still call the variables 
        p := <-c
        fmt.Println(i, "channel value: ", *p)    // we can still call the variables 
    }
    time.Sleep(20 * time.Second)

}

我对为什么会发生这种情况感到非常困惑。只要局部块完成,局部变量就不会失去生命吗?如果我使用的方式是错误的,那么在 Go 中将局部变量传递给外部用户的正确方法是什么?

for 循环 Go 示波器 通道

评论

2赞 Adrian 6/28/2021
频道并不特别;通过通道传递值与将其作为函数参数或赋值语句传递相同。目前还不清楚你的问题到底是关于什么的,但似乎你的困惑是关于指针的。当您传递指向某个值的指针时,任何修改所指向的值都会影响具有指向相同值的指针的任何内容。
2赞 Cerise Limón 6/28/2021
如果有办法访问变量,则该变量是活动变量。块控制范围,而不是生存期。
1赞 Zyl 6/29/2021
当您通过写入地址获取地址时,垃圾收集器将知道并确保只要指向它的指向可以到达,它就会一直存在。使用 Go 本身打破这一点的唯一方法是写入并保留该值,直到将来释放的不确定点,然后尝试访问 .x&xx*intfoo := uintptr(unsafe.Pointer(&x))*x*(*int)(unsafe.Pointer(foo))

答:

4赞 user229044 6/28/2021 #1

Go 是垃圾收集。当不再有对资源的引用(包括当前保存在缓冲通道中的引用)时,资源将被释放。您不必担心释放后使用,并且返回/发送指向“局部”变量的指针也没有错。

只要局部块完成,局部变量就不会失去生命吗?

不,当垃圾收集器没有发现对其价值的进一步引用时,它们就会“失去生命”。超过其封闭作用域的寿命的变量会自动分配在堆上,并且在从封闭作用域返回流且其堆栈内存丢失后可以安全使用。

可以这样想:在 Go 中,没有“局部”变量可以超越其范围。那是不可能的。根据定义,超出声明范围的变量不是“局部”变量,只要有任何东西继续引用它,它就会自动移动到堆中并存在。

值得扩展的内容:

for i := 0; i < 10; i++ {
   dogAddr := produce(i)    // assume we already have: func produce(i int) *Dog 
   c <- dogAddr             // c: channel
}

当循环结束时,狗会立即被释放吗?

你的困惑似乎源于这样一种想法,即变量本身在某种程度上是它所指向的内存的同义词,或者源于一个错误的想法,即指针超出范围会以某种方式导致它指向的内存被回收,而其他事物仍然指向它,这在任何语言中都是不正确的, 垃圾回收与否。dogAddrdogAddr

指针仅包含一个地址。该变量确实在循环的每次迭代中超出范围,但它所保存的值(堆上对象的地址)已按 value 复制到通道中。是的,是一个“局部”变量,但这并不重要。它的值不是“本地”的,它的值是内部分配的某个非本地对象的内存地址。dogAddrDogdogAddrDogprocess()

我在一个简单的代码中对此进行了测试,结果似乎表明局部变量将永远存在。

不,你刚才已经表明,只要你有一点内存的引用,该内存就不会被垃圾回收。

评论

0赞 xc wang 6/28/2021
谢谢。根据你说的,一个无休止的for循环是否有可能产生太多的变量并变成堆栈溢出?如何避免这种情况?
0赞 user229044 6/28/2021
您不能在 (AFAIK) 任何语言中创建任意数量的堆栈分配变量。如果你动态地创建东西,它们就会堆积如山。你可以用尽堆,但这是一个不同的问题,在任何语言中都是可能的。
2赞 Adrian 6/28/2021
堆栈溢出不是由声明太多变量引起的,否则会因内存不足而导致。是的,总是有可能消耗比可用内存更多的内存,但在循环中执行此操作的唯一方法是附加到切片或地图;在循环中声明的变量的范围限定为循环迭代,不会保留到下一次迭代。
0赞 xc wang 6/29/2021
你的回答非常清晰和完美,你准确地指出了我的困惑。谢谢。
0赞 jub0bs 6/29/2021
在 Go 中,没有比其范围更长的“局部”变量这样的东西。不太对;作用域 != 生存期。当函数返回时,可能存在局部变量(在函数中声明的变量),这些变量不再在范围内,但由于对它们的引用仍然存在,因此它们仍然存在。