函数 Main 函数
每个 Go 程序都应该有个main package
main package 里的 main 函数是 Go 程序的入口
Init 函数
init 函数会在包初始化 时运行,仅运行一次
谨慎使用
样例:A 依次依赖 B 和 C ,但 B 也会依赖 C,初始化顺序:C -> B -> A
返回值
支持多值返回
支持命名返回值:被视为定义在函数顶部的变量
调用者可以忽略 部分返回值
回调函数
函数作为参数传入其它函数,并在其它函数内部调用执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { DoOperation(1 , increase) DoOperation(1 , decrease) } func DoOperation (x int , f func (a, b int ) ) { f(x, 1 ) } func increase (a, b int ) { fmt.Println(a + b) } func decrease (a, b int ) { fmt.Println(a - b) }
闭包
闭包为匿名函数,一般没有复用需求
不能独立存在
可以赋值给其它变量
可以直接调用
func(a, b int) { fmt.Println(a + b) }(1, 2)
可以作为函数返回值
1 2 3 4 5 defer func () { if r := recover (); r != nil { fmt.Println("recovered!" ) } }()
至此,函数可以作为:参数、返回值、变量
方法
作用在接收者上的函数
将上下文保存在 receiver 属性,方法可以直接访问 receiver 属性,进而减少参数的传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Human struct { firstName, lastName string } func (h *Human) GetName() string { return h.firstName + "," + h.lastName } func main () { h := new (Human) h.firstName = "Java" h.lastName = "Go" fmt.Println(h.GetName()) }
值传递
与 Java 类似
Go 只有一种规则:值传递
函数内修改参数的值不会影响函数外原始变量的值
可以传递指针参数将变量地址传递给调用函数
Go 会复制该指针作为函数内的地址,但指向同一地址
1 2 3 4 5 6 7 8 9 10 11 aStr := "new string value" pointer := &aStr bStr := *pointer fmt.Println(aStr) fmt.Println(pointer) fmt.Println(bStr) aStr = "changed string value" fmt.Println(aStr) fmt.Println(pointer) fmt.Println(bStr)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type Human struct { name string } func changeHumanSuccess (h *Human, name string ) { h.name = name } func changeHumanFail (h Human, name string ) { h.name = name } func main () { h := Human{name: "Java" } fmt.Println(h) changeHumanSuccess(&h, "Go" ) fmt.Println(h) changeHumanFail(h, "Node.js" ) fmt.Println(h) }
接口
接口定义了一组方法集合
duck typing:struct 无需显式声明实现 interface
一个struct 可以实现多个 interface
interface 中不能定义属性
interface 可以嵌套其它 interface
struct 除了实现 interface 定义的接口外,还可以有额外的方法
interface 可能为 nil,使用 interface 需要先判空,否则可能会触发 nil panic
struct 初始化意味着空间分配,对 struct 的引用不会触发 nil panic
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 type IF interface { getName() string } type Human struct { firstName, lastName string } type Car struct { factory, model string } func (h *Human) getName() string { return h.firstName + "," + h.lastName } func (c *Car) getName() string { return c.factory + "," + c.model } func main () { var ifs []IF h := new (Human) h.firstName = "Java" h.lastName = "Go" ifs = append (ifs, h) c := new (Car) c.factory = "xp" c.model = "p7" ifs = append (ifs, c) for _, i := range ifs { fmt.Println(i.getName()) } }
反射
reflect.TypeOf:返回被检查对象的类型reflect.ValueOf:返回被检查对象的值
1 2 3 4 5 6 m := make (map [string ]string , 10 ) m["a" ] = "b" t := reflect.TypeOf(m) fmt.Println(t) v := reflect.ValueOf(m) fmt.Println(v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 type T struct { A string } func (t T) GetA() string { return t.A } func (t *T) Append(s string ) { t.A = t.A + s } func main () { t := T{A: "a" } v := reflect.ValueOf(t) for i := 0 ; i < v.NumField(); i++ { fmt.Printf("%d: %v\n" , i, v.Field(i)) } for i := 0 ; i < v.NumMethod(); i++ { fmt.Printf("%d: %v -> %v\n" , i, v.Method(i), v.Method(i).String()) } result := v.Method(0 ).Call(nil ) fmt.Println(result) p := reflect.TypeOf(&t) for i := 0 ; i < p.NumMethod(); i++ { fmt.Printf("%d: %v\n" , i, p.Method(i)) } }
OOP
Key
Value
可见性控制
public - 大写 - 跨包使用 private - 小写 - 包内使用
继承
通过组合实现,内嵌一个或多个 struct
多态
通过接口实现
JSON 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 type Human struct { Name string } func main () { human := Human{Name: "zhongmingmao" } marshal := Marshal(&human) fmt.Println(marshal) unmarshal := Unmarshal(marshal) fmt.Println(unmarshal) } func Marshal (human *Human) string { bytes, err := json.Marshal(human) if err != nil { println (err) return "" } return string (bytes) } func Unmarshal (marshal string ) Human { human := Human{} err := json.Unmarshal([]byte (marshal), &human) if err != nil { println (err) } return human }
json 使用 map[string]interface{} 和 []interface{} 保存任意类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 func main () { m := map [string ]interface {}{} m["name" ] = "bom" m["height" ] = 183 marshal := Marshal(&m) fmt.Println(marshal) Unmarshal(marshal) } func Marshal (obj *map [string ]interface {}) string { bytes, err := json.Marshal(obj) if err != nil { println (err) return "" } return string (bytes) } func Unmarshal (s string ) { var obj interface {} err := json.Unmarshal([]byte (s), &obj) if err != nil { println (err) return } if m, ok := obj.(map [string ]interface {}); ok { for k, v := range m { switch value := v.(type ) { case string : fmt.Printf("type of %s is string, value is %v\n" , k, value) case float64 : fmt.Printf("type of %s is float64, value is %v\n" , k, value) case interface {}: fmt.Printf("type of %s is interface{}, value is %v\n" , k, value) default : fmt.Printf("type of %s is wrong, value is %v\n" , k, value) } } } }
error
Go 没有内置的 Exception 机制,只提供了 error 接口
1 2 3 type error interface { Error() string }
error 为 interface,处理时需要判断是否为 nil
1 2 3 e1 := errors.New("Not Found" ) e2 := fmt.Errorf("code %d" , 404 )
借助 struct 实现自定义的 error 归类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type HttpError struct { Code Code } type Code struct { Message string } func (e *HttpError) Error() string { return e.Code.Message } func main () { var es []error es = append (es, &HttpError{Code: Code{Message: "Not Found" }}) }
defer
函数返回之前执行某个语句或者函数,等同于 Java 的 finally,一般是用来关闭资源(防止资源泄露)
1 2 defer file.Close()defer mu.Unlock()
defer 的执行顺序类似于一个栈
1 2 3 4 5 6 7 8 9 func main () { defer fmt.Printf("Java " ) defer fmt.Printf("Go " ) defer fmt.Printf("Rust " ) time.Sleep(time.Second) fmt.Println("main completed" ) }
死锁:fatal error: all goroutines are asleep - deadlock!
1 2 3 4 5 6 7 8 func deadLock () { mutex := sync.Mutex{} for i := 0 ; i < 3 ; i++ { mutex.Lock() defer mutex.Unlock() fmt.Println(i) } }
解决方法:闭包(defer 是在一个函数退出的时候弹栈执行的)
1 2 3 4 5 6 7 8 9 mutex := sync.Mutex{} for i := 0 ; i < 10 ; i++ { go func (i int ) { mutex.Lock() defer mutex.Unlock() fmt.Println(i) }(i) } time.Sleep(time.Second)
panic + recover
panic:可在系统出现不可恢复错误时主动调用 panic,让当前线程直接 crash defer:保证执行,并把控制权交还给接收到 panic 的函数调用者 recover:函数从 panic 中恢复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func main () { panicFunc() fmt.Println("main completed" ) } func panicFunc () { defer func () { fmt.Println("defer func is called" ) if err := recover (); err != nil { fmt.Println(err) } }() panic ("a panic is triggered" ) }
多线程 并发 vs 并行
并发(concurrency):多个事件间隔发生
并行(parallellism):多个事件同时发生
协程 对比 进程
分配系统资源(CPU 时间、内存)的基本单位
有独立的内存空间,切换开销大
线程
线程为进程的一个执行流,是 CPU 调度并能独立运行的基本单位
同一进程中的多线程共享内存空间,切换开销小
多线程通信相对方便
从 Kernel 视角来看,线程本质上是一种特殊的进程
线程与父进程共享:打开的文件、文件系统信息、地址空间、信号处理函数
协程
Go 中轻量级线程实现
Go 在语言层面支持了协程
CSP
Communicating Sequential Process
CSP
两个并发实体通过共享的 channel 进行通信的并发模型
goroutine
goroutine 为轻量级线程,并不对应 OS 的线程 – Java
goroutine 是一种微线程,能够在发现阻塞后启动新的微线程
channel
类似于 Unix 的 Pipe,用于 goroutine 之间的通信和同步
goroutine 之间解耦,但 goroutine 与 channel 耦合
Goroutine vs Thread
默认内存占用少
goroutine - 2KB
thread - 8MB
切换开销小
goroutine - 3 个寄存器
thread - 模式切换(用户态、内核态)、16 个寄存器
并行数量
goroutine - GOMAXPROCS
thread - 受限于 OS
样例 1 2 3 4 5 6 for i := 0 ; i < 3 ; i++ { go func (i int ) { fmt.Println(i) }(i) } time.Sleep(time.Second)
channel
channel 是多个 goroutine 之间通讯的管道
Java 中的线程间通信是基于共享内存
一端发送数据,一端接收数据
同一时间只有一个 goroutine 可以访问数据
协调 goroutine 的执行顺序
1 2 3 4 5 6 7 8 ch := make (chan int ) go func () { ch <- 1 << 4 }() i := <-ch fmt.Println(i)
缓冲区
基于 channel 的通信是同步的
当缓冲区满时,数据的发送是阻塞的
通过 make 关键字创建 channel 时可以定义缓冲区容量(默认为 0)
默认容量为 0,类似于 Java 的 SynchronousQueue
读阻塞写
1 2 3 4 5 6 7 8 9 10 11 12 13 ch := make (chan int ) go func () { time.Sleep(time.Second) fmt.Println("read start" ) _ = <-ch }() ch <- 1 << 4 fmt.Println("write end" )
写阻塞读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ch := make (chan int ) go func () { _ = <-ch fmt.Println("read end" ) }() time.Sleep(time.Second) fmt.Println("write start" ) ch <- 1 << 4 time.Sleep(time.Second)
遍历缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 c := 1 << 2 ch := make (chan int , c) go func () { for i := 0 ; i < c; i++ { rand.Seed(time.Now().UnixNano()) n := rand.Intn(c) fmt.Println("put:" , n) ch <- n } close (ch) }() for v := range ch { fmt.Println("receive:" , v) }
单向
双向 -> 单向
只写 channel:var writeOnly chan<- int 只读 channel:var readOnly <-chan int
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { ch := make (chan int ) go produce(ch) go consume(ch) time.Sleep(time.Second) } func produce (ch chan <- int ) { for { ch <- 0 } } func consume (ch <-chan int ) { for { <-ch } }
关闭
channel 无需每次关闭
关闭的作用:告诉接收者该 channel 再无新数据发送
只有发送者需要关闭 channel
1 2 3 4 5 6 7 8 9 10 11 12 ch := make (chan int ) defer close (ch)go func () { ch <- 0 }() go func () { if v, opened := <-ch; opened { fmt.Println(v) } }()
select
如果所有 channel 都阻塞,则等待或者执行 default,或者随机选择
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func main () { ch1 := make (chan string ) ch2 := make (chan string ) go func () { time.Sleep(time.Second) ch1 <- "one" }() go func () { time.Sleep(1 << 1 * time.Second) ch2 <- "two" }() for i := 0 ; i < 2 ; i++ { select { case msg1 := <-ch1: fmt.Println("receive" , msg1) case msg2 := <-ch2: fmt.Println("receive" , msg2) } } }
timer
time.Ticker 以固定的时间间隔重复地向 channel C 发送时间值 使用场景:为 goroutine 设定超时时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { ch := make (chan int ) timer := time.NewTimer(time.Second) go func () { time.Sleep(1 << 1 * time.Second) ch <- 0 }() select { case <-ch: fmt.Println("receive from ch" ) case <-timer.C: fmt.Println("timeout waiting from ch" ) } }
context
Context 是设置截止日期、同步信号、传递请求相关值的结构体
Context 是 Go 对 goroutine 和 timer 的封装
1 2 3 4 5 6 type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key any) any }
Func
Desc
context.Background
It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.
context.TODO
不确定使用什么 context
context.WithDeadline
超时时间
context.WithValue
向 context 添加键值对
context.WithCancel
创建一个可取消的 context
通过关闭 channel 来传递信号,并停止子协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func main () { messages := make (chan int , 10 ) defer close (messages) go func () { for i := 0 ; i < 10 ; i++ { messages <- i } }() done := make (chan bool ) go func () { ticker := time.NewTicker(time.Second) for range ticker.C { select { case <-done: fmt.Println("child process interrupt..." ) return default : fmt.Printf("receive message: %d\n" , <-messages) } } }() time.Sleep(1 << 2 * time.Second) close (done) time.Sleep(time.Second) fmt.Println("main process exit" ) }
通过 context 关闭子协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func main () { background := context.Background() ctx := context.WithValue(background, "name" , "zhongmingmao" ) go func (c context.Context) { fmt.Println(c.Value("name" )) }(ctx) timeout, cancelFunc := context.WithTimeout(background, time.Second) defer cancelFunc() go func (c context.Context) { for range time.NewTicker(time.Second).C { select { case <-c.Done(): fmt.Println("child process interrupt..." ) return default : fmt.Println("enter default" ) } } }(timeout) select { case <-timeout.Done(): time.Sleep(time.Second) fmt.Println("main process exit" ) } }