1. 并发编程(Concurrency)
Go语言以其强大的并发支持而著名。使用goroutine和channel,可以轻松地实现并发和并行编程。goroutine是轻量级线程,可以同时运行成千上万个,而channel则用于在不同goroutine之间进行通信和同步。
八股时间:
- go routine和进程、线程分别是什么,它们有什么异同?
进程(Process)是操作系统分配资源的基本单位,它是程序的一次执行过程。一个进程包括独立的内存空间、程序代码、数据和执行环境等。每个进程都有自己的地址空间和系统资源,并且通过操作系统的调度器进行调度。进程之间相互独立,通常使用进程间通信(IPC)来实现数据交换。
线程(Thread)是进程内的一个执行单元,也是CPU调度的基本单位。一个进程可以包含多个线程,它们共享同一进程的内存空间和系统资源。线程之间可以共享数据,通过共享内存进行通信,但也需要使用同步机制来保证数据的一致性和安全性。线程由操作系统的线程调度器进行调度。
goroutine是Go语言中的一种轻量级的协程(Coroutine)实现。它是由Go运行时(goroutine调度器)进行调度的,并发执行的单位。与传统的线程相比,goroutine的创建和销毁成本较低,它们拥有更小的栈空间,并且由Go运行时负责调度。goroutine之间通过通道进行通信,实现数据的同步与共享。
- go routine的优势,它是怎么实现的?
- 轻量级:goroutine是非常轻量级的执行单位,创建和销毁的开销很小。相比于传统的线程,可以同时创建成千上万个goroutine,而不会导致系统资源消耗过高。
- 高并发:由于goroutine的轻量级特性,Go语言可以轻松地创建大量的并发执行任务。每个goroutine都可以在独立的执行流中运行,通过并发执行,可以充分利用多核处理器的性能。
- 内置调度器:Go语言的运行时中包含了一个称为"goroutine调度器"的调度器,负责将goroutine分配到系统线程上执行。调度器会自动在多个系统线程之间进行调度和平衡负载,使得程序可以充分利用多核处理器,并且避免了开发者手动管理线程的复杂性。
- 通信机制:goroutine之间通过通道(channel)进行通信,这是Go语言提供的一种并发原语。通道可以安全地在多个goroutine之间传递数据,实现了数据的同步与共享,避免了显式的锁和互斥机制,简化了并发编程的复杂性。
- 错误处理:在Go语言中,每个goroutine都有自己的栈空间和错误处理机制。当一个goroutine发生错误时,它可以选择将错误传递给上层调用者,或者通过通道传递给其他的goroutine进行处理,从而实现了更灵活和可靠的错误处理方式。
实现:goroutine的实现是由Go运行时负责。在程序启动时,Go运行时会为每个CPU核心创建一个系统线程(P),这些线程用于执行goroutine。当创建一个goroutine时,调度器会将其放入一个全局的goroutine队列中。空闲的系统线程会从队列中获取goroutine,并执行它们。如果一个goroutine在执行过程中发生阻塞(如等待IO操作),调度器会将该线程与其关联的goroutine暂停,并将线程释放给其他的goroutine执行,以保持高效的并发执行。
调度器还会根据负载平衡的策略,在不同的系统线程之间重新分配goroutine,以提高系统的整体性能。这种基于协作式调度的机制使得goroutine的切换成本非常低,并且能够充分利用系统资源,实现高并发和高效的并行执行。
例子: 下面这个例子中,使用go func运行了一个匿名函数,该函数不影响主线程的运行,在函数体中,向通道ch发送了数据“42”,并被主线程使用通道读取接收数据。
func main() {
ch := make(chan int)
go func() {
ch <- 42
}()
result := <-ch
fmt.Println(result) // 输出 42
}
2. 错误处理与异常(Error Handling and Exception)
错误处理几乎是Go开发过程中用到最频繁的功能之一,Go语言鼓励使用明确的错误处理方式,而不是传统的异常处理。函数通常返回一个值和一个错误,这样可以更好地控制错误流程。 下面的代码展示了一个错误处理的过程,通过函数的多返回值,可以轻松地捕获错误并进行处理。 我觉得这种实现挺方便的,万物皆可 if err != nil
result, err := someFunction()
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
3. 接口与多态(Interfaces and Polymorphism)
Go语言中的接口是描述了一组方法的签名。通过实现接口,可以实现多态,从而编写更加通用的代码。 我觉得使用接口,能够增加工程的可读性,方便更快速地定位某一段功能。 下方的示例展示了接口的定义和使用
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
还有一些其他的高级特性,我用的不多,就先写这些了。