Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go 开发笔记 #21

Open
CharLemAznable opened this issue Oct 28, 2020 · 5 comments
Open

Go 开发笔记 #21

CharLemAznable opened this issue Oct 28, 2020 · 5 comments
Labels

Comments

@CharLemAznable
Copy link
Owner

go1.13+ 在init中使用了flag, test时报错: flag provided but not defined: -test.XXX

golang 13 changed the order of the test initializer

添加代码

var _ = func() bool {
    testing.Init()
    return true
}()

或直接在flag.Parse()前添加

testing.Init()

golang/go#31859 (comment)

@CharLemAznable
Copy link
Owner Author

使用github.com/go-sql-driver/mysql时, 无法Exec多条sql语句

报错一般为编码1064, 指示为语法错误.

为降低sql注入的风险, multi statements默认为不支持.

sql.Open()方法的参数DataSourceName添加multiStatements=true即可.

@CharLemAznable
Copy link
Owner Author

nil判断迷思

在 Go 语言中,当你将一个空指针(nil)赋值给一个接口类型的变量时,这个接口变量会被初始化为一个非 nil 的接口值。这是因为接口包含三个部分:类型信息、值信息和方法集。即使值信息是 nil,接口本身仍然包含类型信息和方法集,因此整个接口并不是 nil。

示例代码

package main

import "fmt"

type MyStruct struct{}

func main() {
	var ptr *MyStruct = nil
	var iface interface{} = ptr
	fmt.Println(iface == nil) // 输出 false

	iface = nil
	fmt.Println(iface == nil) // 输出 true
}

在赋值语句

var iface interface{} = ptr

执行后,即使 ptr 是 nil,iface 也会包含关于 ptr 类型的信息,即 *MyStruct 类型。

所以要判断接口类型是否为 nil 时,较为稳妥的方法是使用 reflect 获取值信息再进行判断。

示例代码

package main

import (
	"fmt"
	"reflect"
)

type MyStruct struct{}

func main() {
	var ptr *MyStruct = nil
	var iface interface{} = ptr
	fmt.Println(iface == nil) // 输出 false

	v := reflect.ValueOf(iface)
	fmt.Println(!v.IsValid() || v.IsNil()) // 输出 true

	iface = nil
	fmt.Println(iface == nil) // 输出 true

	v = reflect.ValueOf(iface)
	fmt.Println(!v.IsValid() || v.IsNil()) // 输出 true
}

@CharLemAznable
Copy link
Owner Author

float 类型作为 map 的 key

func main() {
	m := make(map[float64]int)
	m[2.4] = 2
	fmt.Printf("k: %v, v: %d\n", 2.4, m[2.4])
	fmt.Printf("k: %v, v: %d\n", 2.400000000001, m[2.400000000001])
	fmt.Printf("k: %v, v: %d\n", 2.4000000000000000000000001, m[2.4000000000000000000000001])
}

output:

k: 2.4, v: 2
k: 2.400000000001, v: 0
k: 2.4, v: 2

当用 float64 作为 key 的时候,先要将其转成 uint64 类型,再插入 key 中。

具体是通过 Float64frombits 函数完成:

// Float64frombits returns the floating point number corresponding
// the IEEE 754 binary representation b.
func Float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) }
func main() {
	fmt.Println(math.Float64bits(2.4))
	fmt.Println(math.Float64bits(2.400000000001))
	fmt.Println(math.Float64bits(2.4000000000000000000000001))
}

output:

4612586738352862003
4612586738352864255
4612586738352862003

2.4 和 2.4000000000000000000000001 经过 math.Float64bits() 函数转换后的结果是一样的。

结论:float 型可以作为 key,但是由于精度的问题,会导致一些诡异的问题,慎用之。

@CharLemAznable
Copy link
Owner Author

优雅地关闭 channel

根据 sender 和 receiver 的个数,分下面几种情况:

  1. 一个 sender,一个 receiver
  2. 一个 sender, M 个 receiver
  3. N 个 sender,一个 receiver
  4. N 个 sender, M 个 receiver

对于 1,2,只有一个 sender 的情况就不用说了,直接从 sender 端关闭就好了,没有问题。重点关注第 3,4 种情况。

第 3 种情形下,优雅关闭 channel 的方法是:the only receiver says “please stop sending more” by closing an additional signal channel

解决方案就是增加一个传递关闭信号的 channel,receiver 通过信号 channel 下达关闭数据 channel 指令。senders 监听到关闭信号后,停止发送数据。

最后一种情况,优雅关闭 channel 的方法是:any one of them says “let’s end the game” by notifying a moderator to close an additional signal channel

和第 3 种情况不同,这里有 M 个 receiver,如果直接还是采取第 3 种解决方案,由 receiver 直接关闭信号 channel 的话,就会重复关闭信号 channel,导致 panic。因此需要增加一个中间人,M 个 receiver 都向中间人 channel 发送关闭数据 channel 的“请求”,中间人收到第一个请求后,就会直接下达关闭数据 channel 的指令(通过关闭信号 channel,这时就不会发生重复关闭的情况,因为信号 channel 的发送方只有中间人一个)。另外,这里的 N 个 sender 也可以向中间人 channel 发送关闭数据 channel 的请求。

另外如果,我们把中间人 channel 的容量声明成 Num(senders) + Num(receivers),因为中间人 channel 容量足够大,所以 senders 和 receivers 可以直接向中间人 channel 发送请求而不用担心阻塞。

@CharLemAznable
Copy link
Owner Author

channel 中的 happened-before 关系

In computer science, the happened-before relation (denoted: ->) is a relation between the result of two events, such that if one event should happen before another event, the result must reflect that, even if those events are in reality executed out of order (usually to optimize program flow).

如果事件 a 和事件 b 存在 happened-before 关系,即 a -> b,那么 a,b 完成后的结果一定要体现这种关系。

关于 channel 的发送(send)、发送完成(send finished)、接收(receive)、接收完成(receive finished)的 happened-before 关系如下:

  1. 第 n 个 send 一定 happened before 第 n 个 receive finished,无论是缓冲型还是非缓冲型的 channel。
  2. 对于容量为 m 的缓冲型 channel,第 n 个 receive 一定 happened before 第 n+m 个 send finished。
  3. 对于非缓冲型的 channel,第 n 个 receive 一定 happened before 第 n 个 send finished。
  4. channel close 一定 happened before receiver 得到通知。

第一条,我们从源码的角度看也是对的,send 不一定是 happened before receive,因为有时候是先 receive,然后 goroutine 被挂起,之后被 sender 唤醒,send happened after receive。但不管怎样,要想完成接收,一定是要先有发送。

第二条,缓冲型的 channel,当第 n+m 个 send 发生后,有下面两种情况:

若第 n 个 receive 没发生。这时,channel 被填满了,send 就会被阻塞。那当第 n 个 receive 发生时,sender goroutine 会被唤醒,之后再继续发送过程。这样,第 n 个 receive 一定 happened before 第 n+m 个 send finished。

若第 n 个 receive 已经发生过了,这直接就符合了要求。

第三条,也是比较好理解的。第 n 个 send 如果被阻塞,sender goroutine 挂起,第 n 个 receive 这时到来,先于第 n 个 send finished。如果第 n 个 send 未被阻塞,说明第 n 个 receive 早就在那等着了,它不仅 happened before send finished,它还 happened before send。

第四条,回忆一下源码,先设置完 closed = 1,再唤醒等待的 receiver,并将零值拷贝给 receiver。

例一:

var done = make(chan bool)
var msg string

func aGoroutine() {
	msg = "hello, world"
	done <- true
}

func main() {
	go aGoroutine()
	<-done
	println(msg)
}

加了 <-done 这行代码后,就会阻塞在此。等 aGoroutine 里向 done 发送了一个值之后,才会被唤醒,继续执行打印 msg 的操作。而这在之前,msg 已经被赋值过了,所以会打印出 hello, world。

这里依赖的 happened before 就是前面讲的第一条。第 n 个 send 一定 happened before 第 n 个 receive finished,即 done <- true 的发生先于 <-done 的完成,这意味着 main 函数里执行完 <-done 后接着执行 println(msg) 这一行代码时,msg 已经被赋过值了,所以会打印出想要的结果。

例二:

var done = make(chan bool)
var msg string

func aGoroutine() {
	msg = "hello, world"
	<-done
}

func main() {
	go aGoroutine()
	done <- true
	println(msg)
}

根据第三条规则,对于非缓冲型的 channel,第 n 个 receive 一定 happened before 第 n 个 send finished。也就是说, 在 done <- true 完成之前,<-done 就已经发生了,也就意味着 msg 已经被赋上值了,最终也会打印出 hello, world。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant