即使panic,也会执行defer
1 2 3 4 5 6 7 8 9 10 func createPost(db *gorm.DB) error { tx := db.Begin() defer tx.Rollback() if err := tx.Create(&Post{Author: "Draveness"}).Error; err != nil { return err } return tx.Commit().Error } 在使用数据库事务时,我们可以使用上面的代码在创建事务后就立刻调用 Rollback 保证事务一定会回滚。哪怕事务真的执行成功了,那么调用 tx.Commit() 之后再执行 tx.Rollback() 也不会影响已经提交的事务。
defer 关键字的调用时机以及多次调用 defer 时执行顺序是如何确定的; defer 关键字使用传值的方式传递参数时会进行预计算,导致不符合预期的结果; func main() { { defer fmt.Println(“defer runs”) fmt.Println(“block ends”) }
fmt.Println("main ends") }
$ go run main....
当程序发生崩溃时只会调用当前 Goroutine 的延迟调用函数也是非常合理的
在其他goroutine中发生panic不recover,会导致整个程序退出(不仅仅是退出该问题goroutine)
当前gorotuinepanic,必须在当前goroutine recover
下面这段代码会发生什么? (为什么不输出 in main ?)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import ( "time" ) func main() { defer println("in main") go func() { defer println("in goroutine ") panic("") }() time.Sleep(1 * time.Second) } 下面这段代码输出什么? (recover要放在defer里)
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 package main import ( "fmt" ) func main() { defer fmt....
make 的作用是初始化内置的数据结构,也就是我们在前面提到的切片、哈希表和 Channel2; new 的作用是根据传入的类型分配一片内存空间并返回指向这片内存空间的指针3;
上下文 context.Context Go 语言中用来设置截止日期、同步信号,传递请求相关值的结构体
树形结构
context与goroutine树
context 包中最常用的方法还是 context.Background、context.TODO,这两个方法都会返回预先初始化好的私有变量 background 和 todo,它们会在同一个 Go 程序中被复用
这两个私有变量都是通过 new(emptyCtx) 语句初始化的,它们是指向私有结构体 context.emptyCtx 的指针,这是最简单、最常用的上下文类型:
这两个变量都是emptyCtx类型,这个两个context,永远不会取消,没有value,没有deadline
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (*emptyCtx) Done() <-chan struct{} { return nil // 意味着永不退出? } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil } context....
同步包sync
Mutex 8字节
1 2 3 4 5 type Mutex struct { state int32 sema uint32 } 互斥锁的状态比较复杂,如下图所示,最低三位分别表示 mutexLocked、mutexWoken 和 mutexStarving,剩下的位置用来表示当前有多少个 Goroutine 在等待互斥锁的释放
在默认情况下,互斥锁的所有状态位都是 0
正常模式 和 饥饿模式 在正常模式下,锁的等待者会按照先进先出的顺序获取锁。但是刚被唤起的 Goroutine 与新创建的 Goroutine 竞争时,大概率会获取不到锁,为了减少这种情况的出现,一旦 Goroutine 超过 1ms 没有获取到锁,它就会将当前互斥锁切换饥饿模式,防止部分 Goroutine 被『饿死』。
在饥饿模式中,互斥锁会直接交给等待队列最前面的 Goroutine。新的 Goroutine 在该状态下不能获取锁、也不会进入自旋状态,它们只会在队列的末尾等待。如果一个 Goroutine 获得了互斥锁并且它在队列的末尾或者它等待的时间少于 1ms,那么当前的互斥锁就会切换回正常模式。
如何从饥饿模式切换到正常模式 加锁和解锁 当锁的状态是 0 时,将 mutexLocked 位置成 1
如果互斥锁的状态不是 0 时就会调用 sync.Mutex.lockSlow 尝试通过自旋(Spinnig)等方式等待锁的释放,该方法的主体是一个非常大 for 循环,这里将它分成几个部分介绍获取锁的过程:
判断当前 Goroutine 能否进入自旋; 通过自旋等待互斥锁的释放; 计算互斥锁的最新状态; 更新互斥锁的状态并获取锁; 自旋是一种多线程同步机制,当前的进程在进入自旋的过程中会一直保持 CPU 的占用,持续检查某个条件是否为真。在多核的 CPU 上,自旋可以避免 Goroutine 的切换,使用恰当会对性能带来很大的增益,但是使用的不恰当就会拖慢整个程序,所以 Goroutine 进入自旋的条件非常苛刻:...
intro 我感觉这本书例子选得不够好
目录索引 第00章-前言 第01章-入门 第02章-程序结构 第03章-基础数据类型 第04章-复合数据类型 第05章-函数 第06章-方法 第07章-接口 第08章-goroutine和channel
简介 Go语言本身只有很少的特性,也不太可能添加太多的特性:
拥有自动垃圾回收、一个包系统、函数作为一等公民、词法作用域、系统调用接口、只读的UTF8字符串等。
go没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。
安装 https://golang.google.cn/doc/install?download=go1.16.5.linux-amd64.tar.gz#install
根据提示下载,然后使用tar命令解压到/usr/local中
1 sudo tar -C /usr/local -xzf 下载的安装包的名字 傻逼go安装包,2021年了,安装没提示(只是单纯的解压),而且还要自己配PATH环境变量,我笑了。配了path环境还要注意要登出登入linux才能起作用。
hello world 1 2 3 4 5 6 7 8 9 package main import ( "fmt" ) func main(){ fmt.Println("suprise! motherfucker") } go中一个package由一个或多个.go文件组成,每个go文件开头申明本文件属于哪个package
main包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在main里的main函数也很特殊,它是整个程序执行时的入口
Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响Go代码的正确解析(译注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字break、continue、fallthrough或return中的一个、运算符和分隔符++、–、)、]或}中的一个)。
举个例子,函数的左括号{必须和func函数声明在同一行上,且位于末尾,不能独占一行,而在表达式x + y中,可在+后换行,不能在+前换行(译注:以+结尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔符,从而导致编译错误)。
gofmt工具把代码格式化为标准格式
goimports,可以根据代码需要,自动地添加或删除import声明
go env 查看当前go的各种配置信息,如GOPATH
命令行参数 (ps:脑子进了一吨水才会这么组织章节) os.Args是一个字符串数组,os.Args[0],是命令本身的名字,和其他编程语言一致
循环 第一种写法:
1 2 3 for i:=1; i<10; i++ { } 注意,不能用++i?...