Golang面试前二夜准备:18-20题
发布于 2021-11-04 14:52 ,所属分类:软件编程学习资料
Golang面试前二夜准备
题号 | 题目 |
---|---|
18 | 怎么限制Goroutine的数量 |
19 | Channel是同步的还是异步的 |
20 | Goroutine和线程的区别 |
18. 怎么限制Goroutine的数量
在Golang中,Goroutine虽然很好,但是数量太多了,往往会带来很多麻烦,比如耗尽系统资源导致程序崩溃,或者CPU使用率过高导致系统忙不过来。
所以我们可以限制下Goroutine的数量,这样就需要在每一次执行go之前判断goroutine的数量,如果数量超了,就要阻塞go的执行。
所以通常我们第一时间想到的就是使用带cap的通道(chan)。每次执行go func(负责消费chan)之前向通道写入值,直到通道满的时候就阻塞了,这样就相当于限制了goroutine的并发数。但是当main结束的时候,因为并不是所有的goroutine都执行完了,在main函数退出之后,这些goroutine就被强制结束了。这个时候我们就需要用到sync.WaitGroup。使用WaitGroup等待所有的goroutine退出。
packagemain
import(
"fmt"
"sync"
)
varchchanint
funcelegance(wg*sync.WaitGroup){
deferwg.Done()
select{
caseret:=<-ch:
fmt.Println(ret)
}
}
funcmain(){
ch=make(chanint,5)//控制并发
wg:=sync.WaitGroup{}
fori:=0;i<10;i++{
wg.Add(1)
ch<-i//五个之后就会阻塞除非有goroutine消费了chan可以继续并发但是上限是5
goelegance(&wg)
}
wg.Wait()
fmt.Println("end")
}
19. Channel是同步的还是异步的
Channel是异步进行的, channel存在3种状态:
nil,未初始化的状态,只进行了声明,或者手动赋值为nil active,正常的channel,可读或者可写 closed,已关闭,千万不要误认为关闭channel后,channel的值是nil
下面我们对channel的三种操作解析:
零值(nil)通道; 非零值但已关闭的通道; 非零值并且尚未关闭的通道。
操作 | 一个零值nil通道 | 一个非零值但已关闭的通道 | 一个非零值且尚未关闭的通道 |
---|---|---|---|
关闭 | 产生恐慌 | 产生恐慌 | 成功关闭 |
发送数据 | 永久阻塞 | 产生恐慌 | 阻塞或者成功发送 |
接收数据 | 永久阻塞 | 永不阻塞 | 阻塞或者成功接收 |
20. Goroutine和线程的区别
从调度上看,goroutine的调度开销远远小于线程调度开销。
OS的线程由OS内核调度,每隔几毫秒,一个硬件时钟中断发到CPU,CPU调用一个调度器内核函数。这个函数暂停当前正在运行的线程,把他的寄存器信息保存到内存中,查看线程列表并决定接下来运行哪一个线程,再从内存中恢复线程的注册表信息,最后继续执行选中的线程。这种线程切换需要一个完整的上下文切换:即保存一个线程的状态到内存,再恢复另外一个线程的状态,最后更新调度器的数据结构。某种意义上,这种操作还是很慢的。
Go运行的时候包涵一个自己的调度器,这个调度器使用一个称为一个M:N调度技术,m个goroutine到n个os线程(可以用GOMAXPROCS来控制n的数量),Go的调度器不是由硬件时钟来定期触发的,而是由特定的go语言结构来触发的,他不需要切换到内核语境,所以调度一个goroutine比调度一个线程的成本低很多。
从栈空间上,goroutine的栈空间更加动态灵活。
每个OS的线程都有一个固定大小的栈内存,通常是2MB,栈内存用于保存在其他函数调用期间哪些正在执行或者临时暂停的函数的局部变量。这个固定的栈大小,如果对于goroutine来说,可能是一种巨大的浪费。作为对比goroutine在生命周期开始只有一个很小的栈,典型情况是2KB, 在go程序中,一次创建十万左右的goroutine也不罕见(2KB*100,000=200MB)。而且goroutine的栈不是固定大小,它可以按需增大和缩小,最大限制可以到1GB。
goroutine没有一个特定的标识。
在大部分支持多线程的操作系统和编程语言中,线程有一个独特的标识,通常是一个整数或者指针,这个特性可以让我们构建一个线程的局部存储,本质是一个全局的map,以线程的标识作为键,这样每个线程可以独立使用这个map存储和获取值,不受其他线程干扰。
goroutine中没有可供程序员访问的标识,原因是一种纯函数的理念,不希望滥用线程局部存储导致一个不健康的超距作用,即函数的行为不仅取决于它的参数,还取决于运行它的线程标识。
相关资源