golang channel+goroutine面试题

1.goroutine泄漏有没有处理,go什么情况下会发生内存泄漏?

可能导致goroutine泄漏的情况:
	1.goroutine陷入死循环,没有释放。goroutine越来越多
	2.channel阻塞,
		one:发送者不发送,导致阻塞
		two:接受者不接收,导致阻塞
		three:向nil channel发送数据,channel未初始化,导致阻塞
		解决方式:
			select {
        case <-ch:
        case <-done:
        return
    }
	3.goroutine锁问题,没有释放锁,其他goroutine等待。越等越多,导致阻塞
	4.wg=sync.WaitGroup wg.add协程数量与真实数量不一致,导致wg.Wait()一直等待
		解决方式:启动一个goroutine就wg.add(1)
	5.初始化时使用contex设置上游超时时间,到下游超时自动结束goroutine

2.goroutine调度用了什么系统调用?GMP

GMP

3.无缓冲 Channel 的发送和接收是否同步?

ch := make(chan int)    无缓冲的channel由于没有缓冲发送和接收需要同步.
ch := make(chan int, 2) 有缓冲channel不要求发送和接收操作同步. 
channel无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读到数据。
channel有缓冲时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。

4.什么是channel,为什么它可以做到线程安全?

Channel是Go中的一个核心类型,可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication),Channel也可以理解是一个先进先出的队列,通过管道进行通信。

Golang的Channel,发送一个数据到Channel 和 从Channel接收一个数据 都是 原子性的。而且Go的设计思想就是:不要通过共享内存来通信,而是通过通信来共享内存,前者就是传统的加锁,后者就是Channel。也就是说,设计Channel的主要目的就是在多任务间传递数据的,这当然是安全的。

5.怎么查看Goroutine的数量?

runtime.NumGoroutine() 获取当前运行中的 goroutine 数量,通过它确认是否发生泄漏
runtime.GOMAXPROCS(5) //设置并行CPU核数最大值5,并返回之前的值

6.Channel是同步的还是异步的?

Channel是异步进行的。

channel存在3种状态:
nil,未初始化的状态,只进行了声明,或者手动赋值为nil
active,正常的channel,可读或者可写
closed,已关闭,千万不要误认为关闭channel后,channel的值是nil

7.怎么限制Goroutine的数量

1.通过channel限制,每次执行的go之前向通道写入值,直到通道满的时候就阻塞了
2.redis分布式锁,初始化100把锁,每次执行的go之前去redis拿一把锁,执行完了再放回去。直到没有锁可以拿为止
3.sync.WaitGroup,Add()加1, Done()减一, Wait()主线程等待 用来控制计数器的数量

8.介绍下 golang 的 runtime 机制?

​ Runtime 负责管理任务调度,垃圾收集及运行环境。同时,Go提供了一些高级的功能,如goroutine, channel, 以及GC。
​ 这些高级功能需要一个runtime的支持. runtime和用户编译后的代码被linker静态链接起来,形成一个可执行文件。

​ 这个文件从操作系统角度来说是一个user space的独立的可执行文件。
​ 从运行的角度来说,这个文件由2部分组成,一部分是用户的代码,另一部分就是runtime。
​ runtime通过接口函数调用来管理goroutine, channel及其他一些高级的功能。从用户代码发起的调用操作系统API的调用都会被runtime拦截并处理。
​ Go runtime的一个重要的组成部分是goroutine scheduler。他负责追踪,调度每个goroutine运行,实际上是从应用程序的process所属的thread pool中分配一个thread来执行这个goroutine。
​ 因此,和java虚拟机中的Java thread和OS thread映射概念类似,每个goroutine只有分配到一个OS thread才能运行。

9.select可以用于什么,常用于gorotine的完美退出

golang 的 select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作
每个case语句里必须是一个IO操作,确切的说,应该是一个面向channel的IO操作

10.主协程如何等其余协程完再操作

方式一:
使用channel进行通信,context,select
select {
  case <-ctx.Done():
  	fmt.Println("child process interrupt...")
  	return
  default:
  	fmt.Printf("send message: %d\n", <-ch)
}


方式二:
var wg sync.WaitGroup
wg.Add(2)  //任务数
wg.Done() //结束
wg.Wait() //等待

11.怎么实现协程完美退出?

通过channel控制生产者,从生产者端关闭channel 
select可以用于什么,常用语gorotine的完美退出

情形一:M个接收者和一个发送者,发送者通过关闭用来传输数据的通道来传递发送结束信号。
情形二:一个接收者和N个发送者,此唯一接收者通过关闭一个额外的信号通道来通知发送者不要再发送数据了。
情形三:M个接收者和N个发送者,它们中的任何协程都可以让一个中间调解协程帮忙发出停止数据传送的信号。

12.channel缓冲长度怎么决定

channel缓冲长度可以与上下游的速度比例成线性关系

13.golang 的channel是怎么实现的

golang的channel是个结构体,里面大概包含了三大部分:
a. 指向内容的环形缓存区,及其相关游标
b. 读取和写入的排队goroutine链表
c. 锁
任何操作前都需要获得锁, 当写满或者读空的时候,就将当前goroutine加入到recvq或者sendq中, 并出让cpu(gopark)。

14.channel底层实现原理?环形数组,CSP模型

CSP并发模型:
概念:以通信的方式来共享内存,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。
channel里面是一个队列,先进先出,负责协程之间通讯
go语言提倡不要通过共享内存来通信,而要通过通信来实现共享内存CSP并发模型

底层:
是一个环形的数组,记录的信息有:
buf:底层缓冲环形数组,有缓冲的channel使用
sendx:下一个数据读取下标
recvx:下一个数据写入下标
sendq:数据读取队列,包含头节点尾节点,记录哪个协程在等待,等待的是哪个channel,等待接收/发送的数据在哪里
recvq:数据写入队列
lock:线程安全锁
qcount:循环数组中元素的数量
datasize:循环数组的长度

环形数组好处:
环形数组消费元素只需要移动下标,普通数组消费元素,需要对剩余的数据前移

15.退出程序时怎么防止channel没有消费完

优雅关闭channel
不要在消费端关闭channel,不要在有多个并行的生产者时对channel执行关闭操作。
也就是说应该只在[唯一的或者最后唯一剩下]的生产者协程中关闭channel,来通知消费者已经没有值可以继续读了。只要坚持这个原则,就可以确保向一个已经关闭的channel发送数据的情况不可能发生。

channel关闭了,仍然可以读取channel剩余的数据

16.Go的channel(有缓冲和无缓冲)

ch <- 666  //向ch管道塞入666
<- ch  // 向ch管道接收值,并丢弃
x := <-ch  //向ch管道中接收数据,并复制给x
x, ok := <-ch  //向ch管道中接收数据,并复制给x,同时检查通道是否已关闭或者是否为空

ch := make(chan string,0) // 定义无缓冲channel
ch := make(chan int, 3)   // 定义有缓冲channel

17.Goroutine和线程的区别?

Goroutine:是用户态的线程,抢占式执行
线程:是操作系统的线程,真正执行者
posted @ 2022-03-03 17:36  Jeff的技术栈  阅读(1289)  评论(0编辑  收藏  举报
回顶部