Golang-内存泄漏例子

以下是我学习中所接触的关于Goroutine内存泄漏的例子

首先内存泄漏的情况会有如下几种:

  • Goroutine 内正在进行 channel/mutex 等读写操作,但由于逻辑问题,某些情况下会被一直阻塞。
  • Goroutine 内的业务逻辑进入死循环,资源一直无法释放。
  • Goroutine 内的业务逻辑进入长时间等待,有不断新增的 Goroutine 进入等待。

demo1:

func main() {
     
	for i := 0; i < 4; i++ {
     
		queryAll()
		fmt.Printf("goroutines: %d\n", runtime.NumGoroutine())
	}
}
func queryAll() int {
     
	ch := make(chan int)
	for i := 0; i < 3; i++ {
     
		go func() {
      ch <- query() }()
	} //开启了三个协程,有两个协程堵塞
	return <-ch
}
func query() int {
     
	n := rand.Intn(100)
	time.Sleep(time.Duration(n) * time.Millisecond)
	return n
}

上面的输出结果是:
goroutines: 3
goroutines: 5
goroutines: 7
goroutines: 10
可看到输出的 goroutines 数量是在不断增加的,每次多 2 个。也就是每调用一次queryAll(),都会泄露 Goroutine。原因在于 无缓冲channel 均已经发送了(每次发送 3 个),但是在接收端并没有完全接收(只接收 1 次 ch),所诱发的 Goroutine 泄露。并且main函数本身也算是一个goroutine

demo2:

func main() {
     
	defer func() {
     
		fmt.Println("goroutines: ", runtime.NumGoroutine())
	}()

	var ch chan struct{
     } //必须make
	go func() {
     
		ch <- struct{
     }{
     } //因为非缓冲?必须得有协程接收它才能存放?
	}()
	//<-ch
	time.Sleep(time.Second)
}

输出结果很明显是:goroutines: 2
抛开管道chan没有事先make在进行使用不说,对于非缓冲管道,必须有接收者才能将数据放入管道,否则就会阻塞!从而导致内存泄漏。

demo3:

func main() {
     
	defer func() {
     
		fmt.Println("goroutines: ", runtime.NumGoroutine())
	}()

	var ch chan int
	go func() {
     
		<-ch
	}()

	time.Sleep(time.Second)
}

与demo2相同道理,无缓冲管道不能只有一方在操作,否则就会堵塞

demo4:

//奇怪的慢等待
func main() {
     
	httpClient := http.Client{
     
		//Timeout: time.Second * 2, //设置httpClicent的超时时间,否则由于响应慢而一直堵塞
	}
	for {
     
		go func() {
     
			resp, err := httpClient.Get("https://www.github.com/")
			if err != nil {
     
				fmt.Printf("http.Get err: %v\n", err)
			} else {
     
				fmt.Printf(resp.Status)
			}
		}()
		time.Sleep(time.Second * 1)
		fmt.Println("goroutines: ", runtime.NumGoroutine())
	}
}

输出结果:(由于在我这经常访问不了github,所以用这个做测试~)
goroutines: 5
200 OK
200 OK
goroutines: 3
goroutines: 4
在上面demo中,是一个 Go 语言中经典的事故场景。也就是一般我们会在应用程序中去调用第三方服务的接口。但是第三方接口,有时候会很慢,久久不返回响应结果。刚好在Go 语言中默认的 http.Client 是没有设置超时时间的。因此就会导致一直阻塞,Goroutine数量 自然也就持续暴涨,不断泄露,最终占满资源,导致事故。
若我们设置了超时时间,若响应很慢也能结束掉这个goroutine,不至于导致内存泄漏!

demo5:

//互斥锁忘记解锁
func main() {
     
	total := 0
	defer func() {
     
		time.Sleep(time.Second)
		fmt.Println("total: ", total)
		fmt.Println("goroutines: ", runtime.NumGoroutine())
	}()
	var mutex sync.Mutex
	for i := 0; i < 10; i++ {
     
		go func() {
     
			mutex.Lock()
			total += 1
		}()
	}
}

上面的代码输出结果如下:
total: 1
goroutines: 10
在上面这个demo中,第一个互斥锁 sync.Mutex 加锁了,由于他可能在处理业务逻辑,或者是忘记 Unlock 解锁了。因此导致后面的所有 sync.Mutex 想加锁,却因锁未释放又都阻塞住了。 建议在加锁后来一句defer mutex.Unlock()

last demo:

//同步锁使用不当
func handle(v int) {
     
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < v; i++ {
     
		fmt.Println("巴拉巴拉巴拉...")
		wg.Done()
	}
	wg.Wait()
}
func main() {
     
	defer func() {
     
		fmt.Println("goroutines: ", runtime.NumGoroutine())
	}()
	go handle(3)
	time.Sleep(time.Second)
}

输出结果:
巴拉巴拉巴拉…
巴拉巴拉巴拉…
巴拉巴拉巴拉…
goroutines: 2
由于使用了同步锁进行编排!但是由于wg.Add的数量与wg.Done数量不匹配,导致wg.Wait一直在等待而导致阻塞!所以可以输出了3个巴拉是传进去参数的结果,但是该goroutine并没有结束,一直在等待还差两个wg.Done没有执行,所以程序结束前有两个goroutine。所以怎么处理这种情况呢,建议在for循环内执行wg.Add(1),defer wg.Done()以及dosomething…

以上就是今天的六个小demo!要坚持每天学习喔!

你可能感兴趣的:(golang核心编程,golang)