性能调优指南
今天是我参加青训营的第六天
Goroutine泄露
Go 中的并发性是以 goroutine(独立活动)和 channel(用于通信)的形式实现的。虽然 goroutine 是轻量级的线程,占用资源很少,但如果一直得不到释放并且还在不断创建新协程,毫无疑问是有问题的,并且是要在程序运行几天,甚至更长的时间才能发现的问题。处理 goroutine 时,程序员需要小心翼翼地避免泄露。如果最终永远堵塞在 I/O 上(例如 channel 通信),或者陷入死循环,那么 goroutine 会发生泄露。即使是阻塞的 goroutine,也会消耗资源,因此,程序可能会使用比实际需要更多的内存,或者最终耗尽内存,从而导致崩溃。让我们来看看几个可能会发生泄露的例子。
语言级别的并发支持是 Go 的一大优势,但这个优势也很容易被滥用。通常我们在开始 Go 并发学习时,常常听别人说,Go 的并发非常简单,在调用函数前加上 go 关键词便可启动 goroutine,即一个并发单元,但很多人可能只听到了这句话,然后就出现了类似下面的代码:
import (
"fmt"
"runtime"
"time"
)
func sayHello() {
for {
fmt.Println("Hello gorotine")
time.Sleep(time.Second)
}
}
func main() {
defer func() {
fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
}()
go sayHello()
fmt.Println("Hello main")
}
很容易看出来,goroutine没有得到释放而陷入了死循环,到source视图搜索drink,发现调用函数时会发生无意义的等待,如果发起的goroutine没有退出,同时不断有新的goroutine产生,对应的内存占用持续增长,CPU系统压力逐渐增大,最后会被系统kill掉。
Block-阻塞&Mutex-锁
互斥锁
Mutex锁又称为互斥锁,要了解互斥锁,就要先了解操作系统中的一些基本概念:
临界资源
- 概念:一次仅允许一个进程使用的共享资源。
临界区
概念:每个进程中访问临界资源的那段程序称之为临界区。 临界区不是内核对象,而是系统提供的一种数据结构,程序中可以声明一个该类型的变量,之后用它来实现对资源的互斥访问。当欲访问某一临界资源时,先将该临界区加锁(若临界区不空闲则等待),用完该资源后,将临界区释放。 补充(待定):分类:临界区也是代码的称呼,所以一个进程可能有多个临界区,分别用来访问不同的临界资源。 内核程序临界资源:系统时钟 普通临界资源:普通I/O设备,如打印机(进程访问这些资源的时候,很慢,会自动阻塞,等待资源使用完成)
互斥量mutex
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
多个线程并发的操作共享变量,会带来一些问题。
- 互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
- 使用互斥锁的好处确保某段关键代码只能由一个线程从头到尾完整地去执行
- 使用互斥锁会影响代码的执行效率,多任务改成了单任务执行
- 互斥锁如果没有使用好容易出现死锁的情况
- 代码给出的示例就是锁没用到该用的地方,造成无端阻塞