Go
Filed under Backend on . Last updated on . Written by Eliza. 12 min read.
简介
原生并发:Goroutine 与 Channel 静态类型语言(所有变量类型在编译时即确定)但支持类型推断 内置垃圾回收(GC):使用并发三色标记-清扫算法 易部署:编译器支持交叉编译(跨平台编译) 应用领域:云原生与基础设施、微服务与 API 开发、CLI 工具开发、网络编程与分布式系统、区块链、AI 与机器学习(新兴领域)
变量、常量 与 Arr, Slice, Map
变量
var b, c int = 1, 2 # 短变量声明是局部变量的初始化声明-仅限函数内部,且必须在该代码块中使用,未使用会编译错误)但全局变量允许声明但不使用 a := 10 //短变量声明 # 全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑 # 类型转换 //数值 var a int = 10 var b float64 = float64(a) //字符串 var str string = "10" var num int num, _ = strconv.Atoi(str)//该函数返回两个值(转换后的值,可能的错误),使用_ 来忽略这个错误常量(组)
const a [int] = 10 //常量定义,可选类型 const ( Unknown = 0 Female = 1 Male = 2 )//常量组定义(实现enum) const ( a = iota // 0 b // 1(省略值,默认使用上一行的 iota) c // 2 d = "ha" // 独立值,iota += 1 )//iota(常量计数器,const的行索引) 从 0 开始,每行加 1 # iota可以参与位移运算:j = 3 << iota // 3 << 1 = 6数组
//初始化一维数组:指定元素类型及个数 var numbers [5]int var numbers = [5]int{1, 2, 3, 4, 5} balance := [5]float32{1:2.0,3:7.0}//指定下标初始化元素 numbers := [...]int{1, 2, 3, 4, 5}//不确定长度的数组用...代替长度 //多维数组 a := [3][4]int{ //三行四列 {0, 1, 2, 3} , /* 第一行索引为 0 */ {4, 5, 6, 7} , /* 第二行索引为 1 */ {8, 9, 10, 11}, /* 第三行索引为 2 */ } b := [2][3][4]int{ //两个(三行四列的平面) { // 第一个平面 {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, }, { // 第二个平面 {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}, }, } //数组的遍历 # _是 Go 语言的空白标识符,意思是:“忽略此值”, 避免未使用变量警告 package main import "fmt" func main() { scores := []int{85, 90, 78, 92} // 1. Use both index and value fmt.Println("Use index and value:") for i, score := range scores { fmt.Printf("Course %d: score = %d\n", i+1, score) } fmt.Println() // 2. Only use value fmt.Println("Only use value:") for _, score := range scores { fmt.Printf("score = %d\n", score) } }切片slice(动态数组)
- new(T) 为类型 T 分配零值存储空间,并返回 *T
- make 专门用于初始化 slice、map 和 channel,返回初始化后的type
//定义 var slice1 []type var slice2 []type = make([]type, len) slice1 := make([]int, len, cap)//len长度,cap容量 //初始化 s :=[] int {1,2,3 } // cap=len=3 s := arr[startIndex:endIndex] //数组的引用:所以说如果两个slice可以共享一个底层数组集合
- 哈希表结构,通过 key 的 hash 来快速定位数据。当多个 key 产生哈希冲突时,runtime 需要进一步进行冲突处理和 key 比较。
# 引用类型;无序;键不存在,返回该类型的零值 //定义:make(map[KeyType]ValueType, initialCapacity) m := make(map[string]int, 10) m := map[string]int{ "apple": 1, "banana": 2, "orange": 3, } //删除 delete(m,"apple")- 普通 Go map 不应该在没有同步机制的情况下进行并发读写,这可能产生数据竞争,并且 runtime 还可能直接检测到并报 fatal error,例如 concurrent map read and map write。
- 如果多个 goroutine 需要读写同一个 map,需要通过 Mutex、RWMutex 等方式同步,或者在适合的场景使用 sync.Map。
- 通用方案:map + Mutex/RWMutex,数据结构和锁策略都由开发者自己控制,可读性也比较直接。
- sync.Map 是标准库提供的并发 Map,但它针对特定并发访问模式进行了优化(例如某些读多写少或者不同 goroutine 操作不同 key 的缓存场景),并不是所有场景都应该使用。
流程、函数、结构体
流程、指针
# 控制流程:if、for 和 switch 三种,没有 while 关键字 # Go 没有三目运算符,所以不支持 ?: 形式的条件判断 // 指针(空指针为nil) package main import "fmt" func main() { var a int= 20 /* 声明实际变量 */ var ip *int /* 声明指针变量 */ ip = &a /* 指针变量的存储地址 */ fmt.Printf("a 变量的地址是: %x\n", &a ) /* 指针变量的存储地址 */ fmt.Printf("ip 变量储存的指针地址: %x\n", ip ) /* 使用指针访问值 */ fmt.Printf("*ip 变量的值: %d\n", *ip ) }函数
//函数使用 func 关键字声明,支持多返回值(Go 错误处理机制的基础) func swap(x, y string) (string, string) { return y, x } # 递归:函数调用自身, 迭代:循环结构重复执行代码块结构体
//没有类的概念,使用 struct(结构体)组织数据,可以为struct类型定义方法 type Books struct { title string author string } Book1 := Books{title: "Go", "Eliza"} PrintBook(&Book1)Go Struct 和 Java Class
- Java class 把字段、方法以及传统面向对象机制结合在一起,并支持类继承
- Go 没有 class,而是使用 struct 表示数据结构,通过给类型定义 method 来添加行为;不支持类继承,更倾向于组合;抽象通常通过 interface 实现—好处:减少复杂的继承层次,让类型之间更多通过组合和接口建立关系
class User { String name; void sayHello() {} }type Logger struct{} func (Logger) Log(msg string) {} type Service struct { Logger }
继承、接口、泛型
接口
- Interface 描述的是一组行为,也就是 method set
- 隐式实现:只要一个类型实现了接口中所有方法,它就自动满足该接口,无需显式声明 implements
//define type Storage interface { Save(data []byte) error } //inplement type PostgresStorage struct{} func (p *PostgresStorage) Save(data []byte) error { return nil }- 实现解耦(-> Java’s DI):Interface 让上层业务依赖抽象行为,而不是依赖具体实现。同时提高可测试性和可替换性。
Service ↓ Storage Interface ↓ ┌───────────┬───────────┐ PostgreSQL Redis Mock泛型
继承
错误处理、文件处理
- 错误处理
- 文件处理
并发
通过 goroutines 和 channels实现并发:
Goroutines:轻量级线程,调度由Golang运行时管理
大量 Goroutine ↓ Go Scheduler ↓ OS Threads ↓ CPUChannel:Go 提供的 Goroutine 之间**数据通信(Communication) + 同步(Sync)**的机制,而不是所有 Goroutine 都直接修改同一份共享状态。
Scheduler:基于 GMP 模型,调度器会将 Goroutine 分配到系统线程中执行,并通过 M 和 P 的配合高效管理并发
- G:Goroutine
- M:系统线程(Machine)
- P:逻辑处理器(Processor)
G G G G G \|/ \|/ P P | | M M | | CPU CPU- Goroutine 怎么被调度到 OS Thread?
- 新创建的 Goroutine 会进入可运行状态,并被放入调度队列。与 P 关联的 M 会从可运行队列获取 G 执行。
- P:Go Scheduler 还会进行**任务窃取(Work Stealing)**等调度,使不同 P 之间的工作量更加均衡。
- G:如果 Goroutine 因为某些阻塞操作无法继续执行,Runtime 会尽量调度其他可运行 Goroutine,提高线程利用率。
Goroutine
解决excution,让多个任务并发执行
vs Thread:Goroutine适合大量I/O并发任务
- 它的初始栈比较小,而且栈可以动态增长
- 大量 Goroutine 可以被 multiplex 到较少的 OS Thread 上
- 调度主要由 Go Runtime 完成,不需要每次都依赖操作系统进行线程级调度 -使用 go 关键字就可以启动一个 Goroutine:
go doSomething(x,y,z)
同一个程序中的所有 goroutine 共享同一个地址空间
Select 语句:使得一个 goroutine 可以等待多个channel,哪个 channel 先准备好,就执行哪个 case。类似于if/else if
func main() { ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(time.Second) ch1 <- "from ch1" }() go func() { time.Sleep(2 * time.Second) ch2 <- "from ch2" }() select { case msg := <-ch1: fmt.Println(msg) case msg := <-ch2: fmt.Println(msg) } }典型用途:Timeout,常用于HTTP请求、数据库查询
//任务完成 vs 2秒超时,谁先发生执行谁 select { case result := <-resultCh: fmt.Println(result) case <-time.After(2 * time.Second): fmt.Println("timeout") }select + default:non-blocking operation
//如果 channel 当前没有数据不会等待,直接走default select { case value := <-ch: fmt.Println(value) default: fmt.Println("no data") }
sync.WaitGroup 用于等待多个 Goroutine 完成
Channel
解决goroutine 之间怎么传递数据和同步
Unbuffered Channel(默认):强调同步,发送端发送数据,必须同时有接收端相应的接收数据
Buffered Channel:允许发送端的数据发送和接收端的数据获取处于异步状态
//声明 chan int # 使用 make 函数创建,使用 <- 操作符发送和接收数据。如果未指定方向,则为双向通道 //默认创建 ch := make(chan int) //带缓冲区创建 ch := make(chan int, 100)close:发送方负责告诉接收方,不会再发送数据
ch := make(chan int) go func() { for i := 0; i < 3; i++ { ch <- i } close(ch) }()- 判断Channel是否关闭
value, ok := <-ch //ok == false时,表示Channel已关闭且缓存数据也读完 if !ok { fmt.Println("channel closed") }
- 判断Channel是否关闭
Sync
解决多个 goroutine 访问共享内存时,怎么保证数据正确
Mutex(Mutual Exclusion互斥锁)
RWMutex
Once
Atomic
类型断言、类型比较
- 类型比较
- == or != bool 整数 浮点数 复数 string pointer channel interface(动态值必须可比较) array(元素可比较) struct(所有字段可比较)
- 不可以用于Slice/Map的内容,只能和 nil 比较