Questions - concurrency

Kesa...大约 4 分钟

1. 有缓冲和无缓冲的 Channel 区别

  • 无缓冲 Channel:
    • 发送:发送时若无接收者则阻塞,直到接收者接收
    • 接收:接收时若无发送者则阻塞,直到发送者发送
  • 有缓冲 Channel:
    • 发送:仅在缓冲以满时阻塞
    • 接收:仅在缓冲为空时阻塞
func main() {
	st := time.Now()
	ch := make(chan bool)
	go func ()  {
		time.Sleep(time.Second * 2)
		<-ch
	}()
	ch <- true  // 无缓冲,发送方阻塞直到接收方接收到数据。
	fmt.Printf("cost %.1f s\n", time.Now().Sub(st).Seconds())
	time.Sleep(time.Second * 5)
}
func main() {
	st := time.Now()
	ch := make(chan bool, 2)
	go func ()  {
		time.Sleep(time.Second * 2)
		<-ch
	}()
	ch <- true
	ch <- true // 缓冲区为 2,发送方不阻塞,继续往下执行
	fmt.Printf("cost %.1f s\n", time.Now().Sub(st).Seconds()) // cost 0.0 s
	ch <- true // 缓冲区使用完,发送方阻塞,2s 后接收方接收到数据,释放一个插槽,继续往下执行
	fmt.Printf("cost %.1f s\n", time.Now().Sub(st).Seconds()) // cost 2.0 s
	time.Sleep(time.Second * 5)
}

2. 简述 协程泄露(Goroutine Leak)

Goroutine Leak 只得时 Goroutine 在创建之后无法停止并被回收,最终导致内存泄露与程序崩溃。

常见场景:

  • 因通道阻塞导致 goroutine 陷入等待,无法结束
 func query() int {
 	ch := make(chan int)
 	for i := 0; i < 1000; i++ {
 		go func() { ch <- 0 }()
 	}
 	return <-ch
 }
 
 func main() {
 	for i := 0; i < 4; i++ {
 		query()
 		fmt.Printf("goroutines: %d\n", runtime.NumGoroutine())
 	}
 }
  • 出现死锁 因 goroutine 之间出现死锁,导致 goroutine 阻塞
  • 无限循环 因 goroutine 陷入无限循环中,没有通知 goroutine 停止导致持续运行

3. GOMAXPROCS 作用

The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit.

GOMAXPROCS 限制的是同时执行用户态 Go 代码的操作系统线程的数量,但是对于被系统调用阻塞的线程数量是没有限制的。

GOMAXPROCS 的默认值等于 CPU 的逻辑核数,同一时间,一个核只能绑定一个线程,然后运行被调度的协程。

使用环境变量GOMAXPROCS或函数runtime.GOMAXPROCS设置。

如 GOMAXPROCS 超过 CPU 核心数,因为同时运行的线程最大为 CPU 核心数,此时反而增加线程切换的开销,降低性能。

对于 I/O 密集型应用,可以适当增大该值以提高 I/O 吞吐率。

4. mutex 有几种模式

mutex 有 正常模式 和 饥饿模式:

  • 正常模式:Goroutine 按照 FIFO 顺序获取锁;刚被唤起的 Goroutine 与新创建的 Goroutine 竞争时,大概率会获取不到锁。
  • 饥饿模式:新 Goroutine 无法获取锁,无法进入自旋,而是在队列末尾等待。
    • 一旦 Goroutine 超过 1ms 没有获取到锁,它就会将当前互斥锁切换饥饿模式,以保证互斥锁的公平性。
    • 若一个 Goroutine 获得了互斥锁并且它在队列的末尾或者它等待的时间少于 1ms,那么当前的互斥锁就会切换回正常模式。

5. Goroutine 何时发生内存泄露

在Go中内存泄露分为暂时性内存泄露和永久性内存泄露。

暂时性内存泄露

  • 获取长字符串中的一段导致长字符串未释放
  • 获取长slice中的一段导致长slice未释放
  • 在长slice新建slice导致泄漏

永久性内存泄露

  • goroutine永久阻塞而导致泄漏
  • time.Ticker未关闭导致泄漏
  • 不正确使用Finalizer(Go版本的析构函数)导致泄漏

6. Go 的竞争条件

所谓竞态竞争,就是当两个或以上的goroutine访问相同资源时候,对资源进行读/写。

可以用go run -race xx.go来进行检测。

解决方法是,对临界区资源上锁,或者使用原子操作(atomics),原子操作的开销小于上锁。

7. 如果若干个goroutine,有一个panic会怎么做?

有一个panic,那么剩余goroutine也会退出,程序退出。如果不想程序退出,那么必须通过调用 recover() 方法来捕获 panic 并恢复将要崩掉的程序。

8. defer 可以捕获子 goroutine 的 panic 么

不可以,必须在子协程中捕获后进行处理或抛出。

上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2