- Goroutine 和线程的区别
Goroutine 是 Go 中并发执行函数或方法的方式。它比线程更轻量级,因为它的创建和销毁的代价更低。与线程相比,Goroutine
在运行时的栈空间要小得多,并且可以根据需要动态增长和缩小。同时,Go 运行时也负责在系统线程上调度所有 Goroutine 的执行,这使得
Goroutine 的使用更加方便且高效。
- Go 的垃圾回收
Go 使用的是标记清除(Mark and Sweep)的垃圾回收策略,这种策略分为“标记”和“清除”两个阶段。在“标记”阶段,GC
会遍历所有的活动对象(也就是还在使用的对象),然后标记它们。在“清除”阶段,GC 会遍历所有的对象,回收那些未被标记(即未被使用)的对象。从
Go 1.5 开始,Go 的 GC 还实现了并发执行的能力,也就是说,在 GC 执行的时候,应用的 Goroutine
依然可以继续运行,只在必要的时候停下来与 GC 进行同步
- Go 中如何使用接口
在 Go 中,接口是定义对象行为的方式,它是一种类型,定义了一组方法,但是没有实现。任何实现了这些方法的类型都被认为实现了该接口。例如:
type Shape interface {
Area() float64
}
type Rectangle struct {
Width, Height float64
}
// Rectangle implements the Shape interface
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
在这个例子中,Rectangle 实现了 Shape 接口,因为它定义了 Area 方法。
- 在 Go 中,何时应该使用指针而不是值
当你需要修改一个函数或方法的接收者,或者当你处理的对象很大并且你想要避免复制该对象时,你应该使用指针。另外,由于在 Go
中,所有的函数参数都是按值传递的,所以如果你想让一个函数修改其参数的值,你也需要使用指针。
- Go 中的切片(slice)和数组的区别
数组是具有固定长度的值序列,而切片则是对数组中的连续片段的引用。切片的长度可以在运行时改变,这使得它比数组更加灵活。数组和切片都是有序的元素集,但是它们在使用方式和用途上有所不同。例如,如果你知道你需要存储的元素数量,那么可以使用数组。如果你不知道你需要多少元素,或者你需要一个可以改变大小的容器,那么你应该使用切片。
- 零值
在 Go 语言中,当我们声明一个变量但不给它赋值时,该变量将会被自动初始化为该类型的零值。各类型的零值如下:
数值类型(int、float64 等)的零值为 0。 布尔类型的零值为 false。 字符串类型的零值为 “”(空字符串)。
指针类型的零值为 nil。 切片、函数、接口、映射(map)和通道(channel)类型的零值也都是 nil。
- 错误处理
Go 语言使用了一个特殊的类型,即 error 类型,来处理错误。你可以通过返回 error 类型的值来表示错误。如果一切正常,就返回
nil,否则返回一个包含错误信息的 error 类型的值。例如:
func divide(x, y float64) (float64, error) {
if y == 0.0 {
return 0.0, errors.New("Cannot divide by zero")
}
return x / y, nil
}
- Channel
Channel 是 Go 语言中用于协程间通信的数据结构,它是一种类型安全的队列。Channel
能够用来发送和接收值,并且这些操作是原子性的。在协程间同步或数据传递时会用到。例如:
func run(EX chan bool,TMax int){
for {
select {
case <-EX:
// 收到停止信号,停止运行任务
tip("计划任务", "计划任务已退出", "./crontab/back.ico")
return
default:
runTask()
ONE1:
for i:=0;i<(3600*TMax);i++{
select{
case <-EX:
EX<-true
break ONE1
default:
time.Sleep(1*time.Second)
}
}
}
}
}
- Map
Map 是 Go
语言的一种内建数据类型,它是键值对的无序集合。键可以是任何可比较的类型,值可以是任何类型。例如,map[string]int
是一个键为字符串、值为整数的 map。map 是引用类型,可以使用 make 函数创建。
- defer 语句
在 Go 中,defer 语句用来延迟执行一个函数或方法,直到包含该 defer 语句的函数执行完毕。defer
常常用于处理成对的操作,如打开和关闭文件,连接和断开数据库,加锁和解锁等。
- 实现继承
Go 语言并没有提供传统的继承机制,而是通过组合和接口来实现代码的重用和多态性。我们可以在结构体中嵌入其他类型,以实现类似继承的效果。
type Animal struct {
Name string
}
func (a *Animal) Move() {
fmt.Printf("%s is moving\n", a.Name)
}
type Bird struct {
Animal // 嵌套Animal结构体,Bird拥有了Animal的所有字段和方法
CanFly bool
}
func (b *Bird) Fly() {
if b.CanFly {
fmt.Printf("%s is flying\n", b.Name)
} else {
fmt.Printf("%s cannot fly\n", b.Name)
}
}
func main() {
b := Bird{}
b.Name = "Sparrow"
b.CanFly = true
b.Move()
b.Fly()
}
- 包(package)
在 Go 中,包是代码的集合,它提供了一种将代码组织为逻辑单元的方式。要在代码中使用其他包,可以通过 import
语句导入,然后通过包名来访问其公开的类型、函数等。
- range 关键字
range 关键字用于 for 循环中迭代数组、切片、字符串、map 或通道等。range 返回索引和元素值。例如:
nums := []int{1, 2, 3, 4, 5}
for i, num := range nums {
fmt.Printf("index: %d, value: %d\n", i, num)
}
对于 map,range 返回键和值;对于通道,range 返回通道中的每个值。
- Go中如何使用反射:在Go中,反射由reflect包提供。反射可以在运行时检查类型和变量,例如它的值,是否可以被设置,它的地址等等。我们甚至可以修改这些值。举个简单的例子
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
- Go的空接口:在Go中,空接口
interface{}不包含任何方法,因此所有类型都实现了空接口。它常被用于容纳任意类型的值。例如
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
func main() {
describe(42)
describe("hello")
}
- Go中的同步原语:Go提供了一些同步原语,如sync.Mutex和sync.RWMutex,用于在协程之间同步访问资源。以下是一个使用互斥锁的例子
var counter = 0
var lock sync.Mutex
func increment() {
lock.Lock()
defer lock.Unlock()
counter++
fmt.Println(counter)
}
- 只运行一次的代码段:在Go中,sync.Once的Do方法可以保证函数只被调用一次。这常被用于单例模式
var once sync.Once
func getInstance() *Singleton {
once.Do(func() {
instance = new(Singleton)
})
return instance
}
- 实现异步操作:Go中的goroutine(通过go关键字)和channel是实现异步操作的主要方式。例如
ch := make(chan int)
go func() {
ch <- doSomething() // doSomething()将在新的goroutine中异步执行
}()
result := <-ch // 从ch接收结果
- 使用context控制并发:Go的context包可以让你发送取消信号给goroutine,或者设置超时。例如
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
select {
case result := <-ch:
fmt.Println("received result", result)
case <-ctx.Done():
fmt.Println("timeout")
}
- Go的内存管理
Go有一个垃圾收集器,会自动释放不再使用的内存。当一个变量不再被引用时,垃圾收集器就会释放它的内存。Go也允许手动管理内存,例如通过使用指针。
- Go协程与其他语言的线程
Go的协程是轻量级的,可以在单个操作系统线程上运行多个协程。协程比线程更轻量级,创建和销毁的成本更低,而且可以更好地利用多核。
- 面向接口编程
在Go中,接口是定义和组织代码的主要工具。接口定义了一组方法,但是没有实现。任何实现了这些方法的类型都被认为实现了该接口。
- 处理panic和recover
Go中的panic函数会停止当前函数的执行,并开始在同一个goroutine中执行panic。recover函数可以捕获到panic,并返回传入panic的值。这样,我们可以在defer函数中使用recover,来捕获和处理panic。
goCopy codefunc main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from", r)
}
}()
panic("a problem occurred")
}