// 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))))
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使用
在golang中的每个模块可以,定义init函数,用来初始化该包内的全局变量,我们可以看看它的特点
优先使用strconv而不是fmt
将原语转换为字符串或从字符串转换时,
strconv
速度比fmt
快。避免字符串到字节的转换
将原语转换为字符串或从字符串转换时,
strconv
速度比fmt
快。不要一劳永逸使用goroutine
一般来说,每个 goroutine:
等待goroutine退出
一个由系统生成的 goroutine, 必须有一种方案能等待 goroutine 的退出。 有两种常用的方法可以做到这一点
另外一种是下面的:
命名风格
包名
当命名包时,请按下面规则选择一个名称:
局部变量的声明
如果将变量明确设置为某个值,则应使用短变量声明形式 (:=)。
nil是一个有效的slice
nil 是一个有效的长度为 0 的 slice,这意味着,
Channel 的 size 要么是 1,要么是无缓冲的
channel 通常 size 应为 1 或是无缓冲的。默认情况下,channel 是无缓冲的,其 size 为零。任何其他尺寸都必须经过严格的审查。我们需要考虑如何确定大小,考虑是什么阻止了 channel 在高负载下和阻塞写时的写入,以及当这种情况发生时系统逻辑有哪些变化。
在边界处拷贝Slices 和 Maps
slices和maps包含了指向底层数据的指针,因此在需要复制它们时要特别注意。请记住,当 map 或 slice 作为函数参数传入时,如果您存储了对它们的引用,则用户可以对其进行修改
使用defer来释放资源
使用 defer 释放资源,诸如文件和锁。
避免字符串到字节的转换
不要反复从固定字符串创建字节slice。相反,请执行一次转换并捕获结果。
尽量初始化时指定Map容量
避免使用全局变量
使用选择依赖注入方式避免改变全局变量。 既适用于函数指针又适用于其他值类型
不要 panic
在生产环境中运行的代码必须避免出现 panic。panic是cascading failures级联失败的主要根源。如果发生错误,该函数必须返回错误,并允许调用方决定如何处理它
避免在公共结构中嵌入类型
这些嵌入的类型泄漏实现细节、禁止类型演化和模糊的文档。假设共享的AbstractList实现了多种列表类型,请避免在具体的列表实现中嵌入 AbstractList。 相反,只需手动将方法写入具体的列表,该列表将委托给抽象列表。
Go 允许类型嵌入作为继承和组合之间的折衷。外部类型获取嵌入类型的方法的隐式副本。 默认情况下,这些方法委托给嵌入实例的同一方法。 结构还获得与类型同名的字段。 所以,如果嵌入的类型是 public,那么字段是 public。为了保持向后兼容性,外部类型的每个未来版本都必须保留嵌入类型。
很少需要嵌入类型。 这是一种方便,可以帮助您避免编写冗长的委托方法 即使嵌入兼容的抽象列表 interface,而不是结构体,这将为开发人员提供更大的灵活性来改变未来,但仍然泄露了具体列表使用抽象实现的细节。
无论是使用嵌入式结构还是使用嵌入式接口,嵌入式类型都会限制类型的演化.
尽管编写这些委托方法是乏味的,但是额外的工作隐藏了实现细节,留下了更多的更改机会,还消除了在文档中发现完整列表接口的间接性操作。
判断接口类型的变量是否相等
具体interface的接口表示
第一个字段功能相似,都是表示类型信息的,而第二个指针字段的功能也相同,都是指向当前赋值给该接口类型变量的动态类型变量的值,我们从printeface和printiface的实现可以看出println会将接口类型变量的类型信息与data信息输出,代码如下:
为了找到真正原因,用lensm工具以图形化方式展示出汇编与源Go代码的对应关系:
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将会读到什么呢?这里分为“有数据关闭”和“无数据关闭”。
golang #networking #public