每日八股文
每日八股-5.29
- Go
- 1.Go语言中panic是什么?如何捕获?不捕获会发生什么?
- 2.子协程panic,父协程会panic吗?
- 3.Go协程和线程区别(分五个方面)
- 4.Channel的底层实现
- 5.Channel的应用场景
- 6.Channel什么情况下会panic?
- 7.Golang不关闭channel会怎样?
- 8.容量为1的channel什么情况下会阻塞?
- 9.Go的错误处理
Go
1.Go语言中panic是什么?如何捕获?不捕获会发生什么?
Panic是一种运行时错误,类似于其它语言中的异常,如果发生了数组越界或者空指针解引用就会报panic错误。通常使用defer匿名自执行函数加上recover来捕获panic,如果不捕获panic程序将直接终止运行。
package mainimport "fmt"func main() {defer func() {if r := recover(); r!= nil {fmt.Println("Recovered from panic:", r)}}()// 模拟可能发生panic的函数调用panicFunction()fmt.Println("After panicFunction call")
}func panicFunction() {fmt.Println("Before panic")panic("Something went wrong!")fmt.Println("After panic (this line will not be executed)")
}
2.子协程panic,父协程会panic吗?
如果在子协程内没有使用defer/recover来捕获异常,那么父协程就会受到影响,程序会终止运行,如果用了defer/recover来捕获异常,父协程可以继续正常执行下去,不受影响。
3.Go协程和线程区别(分五个方面)
- 调度器
Go程用的是runtime,线程用的是操作系统的调度器,线程和携程是m:n的关系,一个线程可以通过处理器(GMP中的P)绑定多个协程 - 创建和销毁的开销
Go程创建和销毁的开销要显著低于线程 - 占用内存的大小
Go程的栈是动态伸缩的,占用空间较小,但线程是固定地,占用空间较大 - 异常处理
Go程出现了异常可以通过当前的Go程去捕获,但线程若出现异常只能通过线程外的机制去捕获 - 同步通信
Go程可以通过channel实现同步通信,而线程一般使用锁或者sync.cond实现同步通信
4.Channel的底层实现
核心是一个叫做hchan的结构体,包括以下字段(具体字段名称不知道,但知道每个字段都代表什么含义)
- 一个表示当前channel元素数量的字段(无缓冲区为0)
- 一个表示当前channel缓冲区大小的字段(无缓冲区为0)
- 一个指向当前channel环形队列的指针(无缓冲区为nil)
- 一个表示当前channel存储元素类型所占的字节数字段
- 一个表示当前channel存储元素数据类型的字段
- 一个指示当前channel下一个该发送数据的位置字段
- 一个指示当前channel下一个该接收数据的位置字段
- 待接收数据的goroutine队列
- 待发送数据的goroutine队列
- Mutex互斥锁
type hchan struct {qcount uint // 当前 channel 中元素的数量 (队列中的元素个数)dataqsiz uint // channel 缓冲区的大小 (即 channel 的容量)buf unsafe.Pointer // 指向 channel 缓冲区数据(一个环形队列)的指针elemsize uint16 // channel 中元素的字节大小closed uint32 // channel 是否已关闭的标志,0 表示未关闭,1 表示已关闭elemtype *_type // channel 中元素的类型(例如 int、string 等)sendx uint // 发送操作在缓冲区中的索引 (下一个发送数据的位置)recvx uint // 接收操作在缓冲区中的索引 (下一个接收数据的位置)// 等待发送的 goroutine 队列 (sendq)// 等待接收的 goroutine 队列 (recvq)// 这两个是 sudog 链表,sudog 代表一个等待的 goroutinerecvq waitq // 等待接收的 goroutine 队列sendq waitq // 等待发送的 goroutine 队列lock mutex // 保护 channel 所有字段的互斥锁
}// waitq 是 sudog 链表的头和尾
type waitq struct {first *sudoglast *sudog
}// sudog 代表一个等待的 goroutine
// 包含指向 goroutine 和其相关数据的指针
type sudog struct {// ... 其他字段 ...g *g // 指向等待中的 goroutine// ... 其他字段 ...
}
5.Channel的应用场景
用来协调并发场景中不同的goroutine之间的通信,通过使用channel,一个goroutine可以向另一个goroutine发送数据,也可以接收数据,channel保证了goroutine之间可以安全的共享数据,并且避免了数据竞争问题。
6.Channel什么情况下会panic?
- 关闭nil的channel,(nil的channel不能做任何操作,读写打开关闭)
Ch:=new(chan int)
Close(*Ch)
这里又牵扯到了make和new的区别,可以一起记忆(比如为什么是*Ch)
- 关闭已经关闭的channel
- 向已经关闭的channel写数据
但是可以从已经关闭的channel读取数据,因为有缓冲区
7.Golang不关闭channel会怎样?
- 如果遍历未关闭的channel,会造成死循环
- 通过close()关闭后,不能再往channel里面写数据,但是可以从channel的缓冲区里读数据
- 不需要通过手动关闭channel来释放资源,当没有goroutine用到channel时,channel会自动回收
8.容量为1的channel什么情况下会阻塞?
当channel已经有数据时,再往channel里面发送数据就会造成发送方阻塞,直到有新的接收方来接收数据
9.Go的错误处理
Go 语言中把错误当成一种特殊的值来处理,不支持其他语言中使用try/catch捕获异常的方式。
Go 语言中使用一个名为 error接口来表示错误类型。
type error interface {Error() string
}
如果需要自定义 error,最简单的方式是使用errors包提供的New函数创建一个错误。
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {s string
}
func (e *errorString) Error() string {return e.s
}
我们还可以自己定义结构体类型,实现error接口
// WrapError 自定义结构体类型
type WrapError struct {s string
}
// WrapError 类型实现error接口
func (e *WrapError) Error() string {return fmt.Sprintf("err:%s", e.s)
}