// bad
p.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount
// 当有多个 return 分支时,很容易遗忘 unlock
// good
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.count
避免字符串到字节的转换
不要反复从固定字符串创建字节 slice。
// bad
for i := 0; i < b.N; i++ {
w.Write([]byte("Hello world"))
}
// good
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data)
}
尽量初始化时指定 Map 容量
make(map[T1]T2, hint)
// bad
m := make(map[string]os.FileInfo)
files, _ := ioutil.ReadDir("./files")
for _, f := range files {
m[f.Name()] = f
}
// good
files, _ := ioutil.ReadDir("./files")
m := make(map[string]os.FileInfo, len(files))
for _, f := range files {
m[f.Name()] = f
}
// $GOROOT/src/runtime/alg.go
func efaceeq(t *_type, x, y unsafe.Pointer) bool {
if t == nil {
return true
}
eq := t.equal
if eq == nil {
panic(errorString("comparing uncomparable type " + t.string()))
}
if isDirectIface(t) {
// Direct interface types are ptr, chan, map, func, and single-element structs/arrays thereof.
// Maps and funcs are not comparable, so they can't reach here.
// Ptrs, chans, and single-element items can be compared directly using ==.
return x == y
}
return eq(x, y)
}
func ifaceeq(tab *itab, x, y unsafe.Pointer) bool {
if tab == nil {
return true
}
t := tab._type
eq := t.equal
if eq == nil {
panic(errorString("comparing uncomparable type " + t.string()))
}
if isDirectIface(t) {
// See comment in efaceeq.
return x == y
}
return eq(x, y)
}
func WaitExample(itemIds []int) int {
wg sync.WaitGroup
results := make([]int, len(itemIds))
for i := 0; i < len(itemIds); i++ {
go (idx int) {
wg.Add(1) // data race
results[idx] = ..
wg.Done()
}(i)
}
wg.Wait() // data race
}
func Do() {
var wg sync.WaitGroup
go func() {
defer clean(&locationErr) // locationErr data race
defer wg.Done()
// ...
}
wg.Wait()
if locationErr != nil { // data race
}
}
unsafe.Pointer
使用 go vet 来保证代码的严谨性
package main
import (
"fmt"
"unsafe"
)
func main() {
// An array of contiguous uint32 values stored in memory.
arr := []uint32{1, 2, 3}
// The number of bytes each uint32 occupies: 4.
const size = unsafe.Sizeof(uint32(0))
// Take the initial memory address of the array and begin iteration.
p := uintptr(unsafe.Pointer(&arr[0]))
for i := 0; i < len(arr); i++ {
// Print the integer that resides at the current address and then
// increment the pointer to the next value in the array.
fmt.Printf("%d ", (*(*uint32)(unsafe.Pointer(p))))
p += size
}
}
表面是会输出 1 2 3,但是使用 go vet
go vet .
# github.com/mdlayher/example
./main.go:20:33: possible misuse of unsafe.Pointer
规则说明:
Converting a Pointer to a uintptr produces the memory address of the value pointed at, as an integer. The usual use for such a uintptr is to print it.
Conversion of a uintptr back to Pointer is not valid in general.
A uintptr is an integer, not a reference. Converting a Pointer to a uintptr creates an integer value with no pointer semantics. Even if a uintptr holds the address of some object, the garbage collector will not update that uintptr’s value if the object moves, nor will that uintptr keep the object from being reclaimed
分裂出来的代码是
p := uintptr(unsafe.Pointer(&arr[0]))
// What happens if there's a garbage collection here?
fmt.Printf("%d ", (*(*uint32)(unsafe.Pointer(p))))
因为把地址值存到了 p 中,没有立即使用,那么可能其中发生了垃圾回收。使用将 unsafe.Point 类型指针转换成 uintpr 后,是不能狗直接转回到 unsafe.Pointer 的,但是只有一种情况例外
If p points into an allocated object, it can be advanced through the object by conversion to uintptr, addition of an offset, and conversion back to Pointer.
也就是执行指针的算术运算逻辑
package main
import (
"fmt"
"unsafe"
)
func main() {
// An array of contiguous uint32 values stored in memory.
arr := []uint32{1, 2, 3}
// The number of bytes each uint32 occupies: 4.
const size = unsafe.Sizeof(uint32(0))
for i := 0; i < len(arr); i++ {
// Print an integer to the screen by:
// - taking the address of the first element of the array
// - applying an offset of (i * 4) bytes to advance into the array
// - converting the uintptr back to *uint32 and dereferencing it to
// print the value
fmt.Printf("%d ", *(*uint32)(unsafe.Pointer(
uintptr(unsafe.Pointer(&arr[0])) + (uintptr(i) * size),
)))
}
}
参考资料
init 使用
错误类型
几种使用场景
无错误匹配,静态错误
有错误匹配,动态错误
错误包装
如果调用其他方法时出现错误,通常有三种处理方式能选择:
如果没有要添加的其他上下文,则按原样返回原始错误。
否则,尽可能在错误消息中添加上下文
错误命名
对于存储为全局变量的错误值,根据是否导出,使用前缀 Err 或 err
对于自定义错误类型,能使用后缀 Error
%q 占位符,它将引用一个值并将其格式化为带有引号的字符串。
一次处理错误
优先使用 strconv 而不是 fmt
strconv
速度比fmt
快。避免字符串到字节的转换
不要一劳永逸使用 goroutine
必须有一种方法能向 goroutine 发出信号它应该停止
等待 goroutine 退出
有两种常用的方法能做到这一点
另外一种是下面的:
包名
局部变量的声明
如果将变量明确设置为某个值,则应使用短变量声明形式 (:=)。
nil 是一个有效的 slice
Channel 的 size 要么是 1,要么是无缓冲的
在边界处拷贝 Slices 和 Maps
当 map 或 slice 作为函数参数传入时,如果存储了对它们的引用,则用户能对其进行修改
使用 defer 来释放资源
使用 defer 释放资源,诸如文件和锁。
避免字符串到字节的转换
尽量初始化时指定 Map 容量
避免使用全局变量
既适用于函数指针又适用于其他值类型
不要 panic
如果发生错误,该函数必须返回错误,并允许调用方决定如何处理它
避免在公共结构中嵌入类型
这些嵌入的类型泄漏实现细节、禁止类型演化和模糊的文档。
假设共享的AbstractList实现了多种列表类型,请避免在具体的列表实现中嵌入 AbstractList。
相反,只需手动将方法写入具体的列表,该列表将委托给抽象列表。
Go 允许类型嵌入作为继承和组合之间的折衷。
外部类型获取嵌入类型的方法的隐式副本。
默认情况下,这些方法委托给嵌入实例的同一方法。
结构还获得与类型同名的字段。
很少需要嵌入类型。
无论是使用嵌入式结构还是使用嵌入式接口,嵌入式类型都会限制类型的演化.
尽管编写这些委托方法是乏味的,但是额外的工作隐藏了实现细节,留下了更多的更改机会,
还消除了在文档中发现完整列表接口的间接性操作。
判断接口类型的变量是否相等
具体 interface 的接口表示
Go 都会调用 runtime.ifaceeq 来进行比较!
这回对于接口类型变量的相等性判断一目了然了 (由 efaceeq 中 isDirectIface 函数上面的注释可见)!
常见的数据竞争
循环索引变量捕获导致的 data race
由于错误变量捕获导致的 data race
由于命名的返回变量被捕获,导致数据 data race。
slice 是令人困惑的类型,会产生难以察觉的的 data race
对 map 并发读写导致了频繁的 data race
混合使用 channel 和共享内存使代码变得复杂,容易发生 data race
sync.WaitGroup 不正确的 Add 或者 Done 产生 data race
unsafe.Pointer
使用 go vet 来保证代码的严谨性
表面是会输出 1 2 3,但是使用 go vet
规则说明:
分裂出来的代码是
因为把地址值存到了 p 中,没有立即使用,那么可能其中发生了垃圾回收。使用将 unsafe.Point 类型指针转换成 uintpr 后,是不能狗直接转回到 unsafe.Pointer 的,但是只有一种情况例外
也就是执行指针的算术运算逻辑
这样使用后,就正常了
golang 中的 socket 编程
socket 无数据
如果对方未发送数据到 socket,接收方 (Server) 会阻塞在 Read 操作上,这和前面提到的“模型”原理是一致的。执行该 Read 操作的 goroutine 也会被挂起。runtime 会监视该 socket,直到其有数据才会重新调度该 socket 对应的 Goroutine 完成 read
有部分数据
如果 socket 中有部分数据,且长度小于一次 Read 操作所期望读出的数据长度,那么 Read 将会成功读出这部分数据并返回,而不是等待所有期望数据全部读取后再返回
Socket 中有足够数据
如果 socket 中有数据,且长度大于等于一次 Read 操作所期望读出的数据长度,那么 Read 将会成功读出这部分数据并返回。这个情景是最符合对 Read 的期待的了:Read 将用 Socket 中的数据将传入的 slice 填满后返回:n = 10, err = nil。
socket 关闭
如果 client 端主动关闭了 socket,那么 Server 的 Read 将会读到什么呢?这里分为“有数据关闭”和“无数据关闭”。
type/golang #public