wrap errors 会把底层的所有错误(包括根源错误以及堆栈信息)打包,在顶层一次性打印出来。这样我们就不用在每一层都做日志输出和错误处理,也保证了一次错误只打印一次错误日志。
%v 和%+v
%v 只输出字段的value
%+v 会以键值对的形式输出字段的key和value
一般在程序代码(自己项目)中和第三方库、标准库、kit库(代码基础库)交互才调用wrap errors;包内的调用不会用wrap errors,避免用户也使用wrap errors造成日志的双倍打印,往往采用降级或者往上抛的处理方式。
最底层的调用会保存整个调用过程的堆栈信息,所以只需要在出错的地方调用一次error即可获取完整的堆栈。
sync.Pool:用于高频内存分配场景,保存和复用临时对象,可以减少内存分配,降低GC压力,只能存放一些可被随时回收的对象。
处理请求时往往会额外开启goroutine来向后端请求数据,为了控制这些goroutine的生命周期,或者向这些goroutine传入信息,引出了context包。
作用:传递数据,超时控制,取消goroutine。
CPU/编译器 为了提高运行速率会对代码进行重排,重排后,单线程下不会影响运行结果(不会出现逻辑错误),但是多个goroutine下,就可能会出现错误。
在cpu和内存之间有多级cache,cpu运行的数据不会立即写到内存中,而是会写入cpu的cacheline中,接着返回继续执行下一条指令。这就导致在数据写到内存前,不同goroutine之间的数据存在不可见性。
机器字由操作系统决定,是机器一次读写的位数,32位操作系统是32bit -> 4byte,64位操作系统是8byte。超过机器字大小的变量的读写不是原子的,编译的顺序由于内存重排是不确定的。
内存可见性:由于内存重排的存在,即便是原子读写也不一定是可见的。
memory barrier会使得数据在写入到cacheline后,立马扩散到内存,之后再执行其他对内存的操作。这样就保证了多个goroutine之间数据可见。
使用os.exit()退出后,defer不会执行。
在创建goroutine时,它的生命周期要明确,要有安全关闭的手段。
goroutine做并发时,要由调用者自己来开,函数的提供者不应该假设这种并发情况。
interface(接口)类型:包含两个指针:Type(指向实现接口的struct)和Data(指向实际的值)
type struct{
Type uintptr
Data uintptr
}
string类型包含两个成员:指向byte数组的指针和长度(int类型)
最晚加锁,最早释放,锁里面的内容越少越好(临界区的内容越少越好)
写时复制容易出现资源竞争问题,常用Atomic(原子替换)来解决写时复制时读多写少的情况。
具体方法:写操作时复制全量老数据到一个新的对象中,携带上本次新写的数据,之后利用原子替换。
锁饥饿:指某个goroutine很可能长时间取不到锁。