Skip to content

Instantly share code, notes, and snippets.

@xin053
Last active February 5, 2021 02:45
Show Gist options
  • Save xin053/b2b0b4264443592b685d50151c061937 to your computer and use it in GitHub Desktop.
Save xin053/b2b0b4264443592b685d50151c061937 to your computer and use it in GitHub Desktop.
[go 陷阱]go 陷阱 #go #陷阱
// 闭包 trap
var rmdirs []func()
for _, d := range tempDirs() {
dir := d // 不能删除,否则循环删除的是同一个目录
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir)
})
}
for _, rmdir := range rmdirs {
rmdir()
}
// 因为 for 循环引入了新的词法块, d 在这个词法块中被声明,而闭包(函数值)中记录的是循环变量的内存地址,而不是某一时刻的值,所以引入一个新的变量,将 d 赋值给 dir
// 这并不是循环的问题,而是闭包(函数值)等待循环结束之后才执行
// 另外的解决方法,将 d 作为参数传递给闭包(函数值)
for _, dir := range tempDirs() {
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func(dir string) {
os.RemoveAll(dir)
}(dir))
}
// defer problem
for i := 0; i < 3; i++ {
defer func() {
fmt.Println("b:", i)
}()
}
// fix1
for i := 0; i < 3; i++ {
defer func(i int) {
fmt.Println("b:", i)
}(i)
}
// fix2
for i := 0; i < 3; i++ {
i := i
defer func() {
fmt.Println("b:", i)
}()
}
// go routine problem
for i := 0; i < 3; i++ {
go func() {
fmt.Println("b:", i)
}()
}
// fix1
for i := 0; i < 3; i++ {
go func(i int) {
fmt.Println("b:", i)
}(i)
}
// fix2
for i := 0; i < 3; i++ {
i := i
go func() {
fmt.Println("b:", i)
}()
}
func main() {
t := T{y: [3]int16{123, 456, 789}}
p := unsafe.Pointer(&t)
// ty2 := (*int16)(unsafe.Pointer(uintptr(p)+N+M+M))
addr := uintptr(p) + N + M + M
// Now the t value becomes unused, its memory may be
// garbage collected at this time. So the following
// use of the address of t.y[2] may become invalid
// and dangerous!
ty2 := (*int16)(unsafe.Pointer(addr))
fmt.Println(*ty2)
}
// 解决方法
ty2 := (*int16)(unsafe.Pointer(uintptr(p)+N+M+M))
// or
func main() {
t := T{y: [3]int16{123, 456, 789}}
p := unsafe.Pointer(t)
addr := uintptr(p) + N + M + M
ty2 := (*int16)(unsafe.Pointer(addr))
// This following line ensures the memory of
// the value t will not get garbage collected
// currently for sure.
runtime.KeepAlive(p)
fmt.Println(*ty2)
}
// for 循环多次 defer,导致栈持续增长,占用内存过大
func writeManyFiles(files []File) error {
for _, file := range files {
f, err := os.Open(file.path)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(file.content)
if err != nil {
return err
}
err = f.Sync()
if err != nil {
return err
}
}
return nil
}
// 可将 defer 放到匿名函数中解决
func writeManyFiles(files []File) error {
for _, file := range files {
if err := func() error {
f, err := os.Open(file.path)
if err != nil {
return err
}
// The close method will be called at
// the end of the current loop step.
defer f.Close()
_, err = f.WriteString(file.content)
if err != nil {
return err
}
return f.Sync()
}(); err != nil {
return err
}
}
return nil
}
package main
import "time"
func main() {
for {
select {
case <-time.After(3 * time.Second):
default:
}
}
}
// After 产生的 timer 需要到达 3s 之后才会释放, 而短时间内 for 循环会产生大量 timer, 这些 timer 都不会立即释放,所以程序占用很大内存,需要在 timer 不需要时手动释放, fix 如下
package main
import "time"
func main() {
for {
t := time.NewTimer(3*time.Second)
select {
case <- t.C:
default:
t.Stop()
}
}
}
// 或者如果不想每次 for 中 new 一个 timer,可以使用 timer.Reset 来实现
func longRunning(messages <-chan string) {
timer := time.NewTimer(time.Minute)
defer timer.Stop() //A time.Timer value can be leaved in non-stopping status when it is not used any more, but it is recommended to stop it in the end.
for {
select {
case <-timer.C: // expires (timeout)
return
case msg := <-messages:
fmt.Println(msg)
// This "if" block is important.
if !timer.Stop() {
<-timer.C // 如果 Stop() 返回 false(timer 到期), 那么清空 C 中的值,因为 Reset 并不会清空已经发送到 C 中的值
}
}
// Reset to reuse.
timer.Reset(time.Minute)
}
}
// Except sometimes we deliberately let the main goroutine in a program hanging to avoid the program exiting, most other hanging goroutine cases are unexpected. It is hard for Go runtime to judge whether or not a goroutine in blocking state is hanging or stays in blocking state temporarily, so Go runtime will never release the resources consumed by a hanging goroutine.
// In the first-response-wins channel use case, if the capacity of the channel which is used a future is not large enough, some slower response goroutines will hang when trying to send a result to the future channel. For example, if the following function is called, there will be 4 goroutines stay in blocking state for ever.
func request() int {
c := make(chan int)
for i := 0; i < 5; i++ {
i := i
go func() {
c <- i // 4 goroutines will hang here.
}()
}
return <-c
}
// To avoid the four goroutines hanging, the capacity of channel c must be at least 4.
// In practice, values of the types (except the Locker interface values) in the sync standard package should never be copied. We should only copy pointers of such values.
// The following is bad concurrent programming example. In this example, when the Counter.Value method is called, a Counter receiver value will be copied. As a field of the receiver value, the respective Mutex field of the Counter receiver value will also be copied. The copy is not synchronized, so the copied Mutex value might be corrupted. Even if it is not corrupted, what it protects is the use of the copied field n, which is meaningless generally.
import "sync"
type Counter struct {
sync.Mutex
n int64
}
// This method is okay.
func (c *Counter) Increase(d int64) (r int64) {
c.Lock()
c.n += d
r = c.n
c.Unlock()
return
}
// The method is bad. When it is called,
// the Counter receiver value will be copied.
func (c Counter) Value() (r int64) {
c.Lock()
r = c.n
c.Unlock()
return
}
// We should change the receiver type of the Value method to the pointer type *Counter to avoid copying sync.Mutex values.
var s0 string // a package-level variable
// A demo purpose function.
func f(s1 string) {
s0 = s1[:50]
// Now, s0 shares the same underlying memory block
// with s1. Although s1 is not alive now, but s0
// is still alive, so the memory block they share
// couldn't be collected, though there are only 50
// bytes used in the block and all other bytes in
// the block become unavailable.
}
func demo() {
s := createStringWithLengthOnHeap(1 << 20) // 1M bytes
f(s)
}
// fix
func f(s1 string) {
s0 = string([]byte(s1[:50]))
}
// or
func f(s1 string) {
s0 = strings.Repeat(s1[:50], 1)
}
// Similarly to substrings, subslices may also cause kind-of memory leaking. In the following code, after the g function is called, most memory occupied by the memory block hosting the elements of s1 will be lost (if no more values reference the memory block).
var s0 []int
func g(s1 []int) {
// Assume the length of s1 is much larger than 30.
s0 = s1[len(s1)-30:]
}
// fix
func g(s1 []int) {
s0 = append([]int(nil), s1[len(s1)-30:]...)
// Now, the memory block hosting the elements
// of s1 can be collected if no other values
// are referencing the memory block.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment