函数 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" ) } }