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

huangapple go评论75阅读模式
英文:

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

问题

特别是当变量在局部作用域内生成时,它的生存时间有多长?

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

for i := 0; i < 10; i++ {
    dogAddr := produce(i)    // 假设我们已经有了:func produce(i int) *Dog 
    c <- dogAddr             // c: 通道
}

当循环结束时,这些狗会立即被释放吗?它们只会在等待被消耗的神奇时间内存活,然后在被消耗后被释放吗?

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

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++ {                     // 这是生成循环
		x := i
		m[i] = &x
		fmt.Println(i, "mapping to: ", &a)
		c <- &x
	}                                            // 生成循环在这里结束
	for i := 0; i < 10; i++ {
		fmt.Println(i, "stored pointer: ", m[i]) // 我们仍然可以调用这些变量
	}
	for i := 0; i < 10; i++ {
		fmt.Println(i, "stored value: ", *m[i])  // 我们仍然可以调用这些变量
		p := <-c
		fmt.Println(i, "channel value: ", *p)    // 我们仍然可以调用这些变量
	}
	time.Sleep(20 * time.Second)
}

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

英文:

Especially when the variable is generated inside a local scope. How long is its survival time?

For example, given a loop creates 10 dogs and pass the pointers along into a channel, like

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

When the loop ends, will the dogs be released immediately? will they survive only for a magic time to wait for being consumed, and Will they be released after being consumed?

I tested this in a simple code, and results seems to show that the local variables will survive forever.

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)
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 &lt; 10; i++ {                     // this is generation-loop
		x := i
		m[i] = &amp;x
		fmt.Println(i, &quot;mapping to: &quot;, &amp;a)
		c &lt;- &amp;x
	}                                            // the generation-loop breaks here
	for i := 0; i &lt; 10; i++ {
		fmt.Println(i, &quot;stored pointer: &quot;, m[i]) // we can still call the variables 
	}
	for i := 0; i &lt; 10; i++ {
		fmt.Println(i, &quot;stored value: &quot;, *m[i])  // we can still call the variables 
		p := &lt;-c
		fmt.Println(i, &quot;channel value: &quot;, *p)    // we can still call the variables 
	}
	time.Sleep(20 * time.Second)

}

I am very confused on why this could happens. Aren't local variables lose their life as long as local blocks get finished? If the way I used is wrong, then what is the correct way to pass local variables to an outer user in Go?

答案1

得分: 4

Go语言是具有垃圾回收机制的。当没有任何引用指向资源时,包括当前在缓冲通道中持有的引用,这些资源将被释放。你不必担心使用后释放,返回或发送指向“局部”变量的指针也没有问题。

局部变量不是在局部块结束时失去作用吗?

不,当垃圾回收器找不到对其值的进一步引用时,它们才会“失去作用”。超出其封闭作用域而存在的变量会自动分配在堆上,并且在流程从封闭作用域返回并且其堆栈内存丢失后仍然可以安全使用。

可以这样理解:在Go语言中,不存在超出作用域而仍然存在的“局部”变量。这是不可能的。在声明变量的作用域之外仍然存在的变量被定义为不是在那个意义上的“局部”变量,它们会自动移动到堆上,并且只要有任何引用它们,它们就会一直存在。

值得扩展的一点是:

for i := 0; i < 10; i++ {
   dogAddr := produce(i)    // 假设我们已经有了:func produce(i int) *Dog 
   c <- dogAddr             // c: 通道
}

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

你的困惑似乎源于dogAddr变量本身与它所指向的内存之间的关系,或者错误地认为dogAddr指针超出作用域会导致它所指向的内存在其他地方仍然有引用时被回收,这在任何语言中都不是真实的,无论是否有垃圾回收机制。

dogAddr指针只是包含一个地址的变量。每次循环迭代时,该变量确实会超出作用域,但它所持有的值(指向堆上的Dog对象的地址)已经按值复制到通道中。是的,dogAddr是一个“局部”变量,但这并不重要。它的值不是“局部”的,它的值是一个内存地址,指向在process()内部分配的某个非局部Dog对象。

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

不,你只是证明了只要你有对一块内存的引用,那块内存就不会被垃圾回收。

英文:

Go is garbage collected. Resources are freed when there are no more references to them, including references currently held in buffered channels. You don't have to worry about use-after-free, and there is nothing wrong with returning/sending pointers to "local" variables.

> Aren't local variables lose their life as long as local blocks get finished?

No, they "lose their life" when the garbage collector finds no further references to their value. Variables that outlive their enclosing scope are automatically allocated on the heap and are safe to use after flow returns from the enclosing scope and its stack memory is lost.

Think of it this way: There is no such thing in Go as a "local" variable that outlives its scope. That is impossible. A variable that lives beyond the scope in which it is declared is by definition not "local" in that sense, it is automatically moved to the heap and lives as long as anything continues to reference it.

Something worth expanding on:

> for i := 0; i < 10; i++ {
> dogAddr := produce(i) // assume we already have: func produce(i int) *Dog
> c <- dogAddr // c: channel
> }
> When the loop ends, will the dogs be released immediately?

Your confusion seems to stem either from the idea that the dogAddr variable itself is somehow synonymous with the memory it points to, or from the mistaken idea that the dogAddr pointer going out of scope would somehow cause the memory it points to to be reclaimed while other things still point to it, which isn't true in any language, garbage collection or not.

The dogAddr pointer just contains an address. That variable does go out of scope each iteration of the loop, but the the value it holds (the address of a Dog object on the heap) has already been copied by value into the channel. Yes, dogAddr is a "local" variable, but that's not really relevant. Its value is not "local", its value is a memory address for some non-local Dog object allocated down inside process().

> I tested this in a simple code, and results seems to show that the local variables will survive forever.

No, you've just shown that, as long as you have a reference to a bit of memory, that memory will not be garbage collected.

huangapple
  • 本文由 发表于 2021年6月28日 23:48:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/68166372.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定