先从下面的一段代码开始
下面的代码是模拟了启动多线程去查数据库,之后汇总的操作
数据库:mysql
orm框架:ent
代码:
package main
import (
"awesomeProject/ent"
"awesomeProject/ent/user"
"context"
"fmt"
"github.com/go-sql-driver/mysql"
"log"
"sync"
)
func main() {
config := mysql.Config{
User: "root",
Passwd: "toor333666",
Net: "tcp",
Addr: "localhost:3306",
DBName: "test",
AllowNativePasswords: true,
}
client, err := ent.Open("mysql", config.FormatDSN())
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()
// 创建表
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
// 初始化数据
ctx := context.Background()
for i := 0; i < 10; i++ {
for j := 0; j < 3; j++ {
client.User.Create().SetName(fmt.Sprintf("name-%d",j)).SetAge(j).SaveX(ctx)
}
}
// 查找数据,比如这里相差各个年龄段的数据,之后做汇总,
searchAges := []int{1,2,122}
users := make([]interface{}, len(searchAges))
var waitGroup sync.WaitGroup // 这就是java中的countdownlatch
waitGroup.Add(len(searchAges))
for idx, age := range searchAges {
go func(idx,age int) {
var ts []*ent.User
err := client.User.Query().Where(user.AgeEQ(age)).Select(
user.FieldID,user.FieldName,user.FieldAge).Scan(ctx,&ts)
if err != nil{
users[idx] = err
}else{
users[idx] = ts
}
waitGroup.Done()
}(idx,age)
}
waitGroup.Wait()
res := make([]*ent.User, 0)
for _, item := range users {
if err,ok:= item.(error);ok{
panic(err)
}
if users,ok := item.([]*ent.User);ok{
res = append(res, users...)
}
}
println(fmt.Sprintf("%v", res))
client.User.Delete().ExecX(ctx)
}
go.mod如下:
module awesomeProject
go 1.18
require (
entgo.io/ent v0.11.4
github.com/go-sql-driver/mysql v1.6.0
)
require (
ariga.io/atlas v0.7.3-0.20221011160332-3ca609863edd // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl/v2 v2.13.0 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/zclconf/go-cty v1.8.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/text v0.3.7 // indirect
)
代码很简单,有疑问的地方如下如所示:
上面的代码中searchAges
参数122是没有数据的,所以结果中必然有一个是没有数据的,但item为nil,断言居然成功了。
那么,接下来就说说go中nil。
从感觉上来看,会将它和Java中的null联系起来,但go的nil和其他语言中(Java,Python)的null可不是一回事。
go中,变量声明就有默认值,这个值叫做0值,
0
for numeric types,false
for the boolean type, and""
(the empty string) for strings.nil
for interfaces, slices, channels, maps, pointers and functions.go中其他的标志符基本都有一个默认的类型比如,
比如false,true它们的类型是bool类型,iota的类型是int。
nil没有默认的类型,编译器会从上下文的信息中来推断他的类型,并且在之前
代码:
// 下面的代码会编译成功,编辑器可以从上下文中推断出nil的类型
_ = (*struct{})(nil) // 这里代码的意思是类型转化,这里能转化不报错,说明类型是正确的。也就是说nil的类型和强转的类型是一样的
_ = []int(nil)
_ = map[int]bool(nil)
_ = chan string(nil)
_ = (func())(nil)
_ = interface{}(nil)
// 下面的和上面的等价的
var _ *struct{} = nil
var _ []int = nil
var _ map[int]bool = nil
var _ chan string = nil
var _ func() = nil
var _ interface{} = nil
// 编译会失败,信息不足够,编译器也不知道是什么类型。
var _ = nil
nil值在一个上下文中可以被替换掉,代码如下
nil := 123
fmt.Println(nil) // 123
// 下面的编译会失败,在当前的上下文中
// nil已经是一个int的值了,代表的是int类型
var _ map[string]int = nil
同一类型的所有值的内存布局总是相同的。此类型下的 nil和非nil的值大小相等,但是不同类型的nil可能不一样。
代码:
package main
import (
"fmt"
"unsafe"
)
func main() {
var p *struct{} = nil
fmt.Println( unsafe.Sizeof( p ) ) // 8
var s []int = nil
fmt.Println( unsafe.Sizeof( s ) ) // 24
var m map[int]bool = nil
fmt.Println( unsafe.Sizeof( m ) ) // 8
var c chan string = nil
fmt.Println( unsafe.Sizeof( c ) ) // 8
var f func() = nil
fmt.Println( unsafe.Sizeof( f ) ) // 8
var i interface{} = nil
fmt.Println( unsafe.Sizeof( i ) ) // 16
}
上面运行的结果不是不变的,编译器和架构会影响。以上打印的结果适用于64位体系结构和标准 Go 编译器。对于32位体系结构,打印大小将减半。
例如,下面示例中的两个比较都无法编译。原因是,在每次比较中,两个操作数都不能隐式转换为另一个操作数的类型。
go的比较规则可以看这个官网文档
简而言之。如果其中一个值可以隐式转换为另一个值的类型,则两个值是可比的。
代码:
// 编译会失败,类型不匹配
var _ = (*int)(nil) == (*bool)(nil) // error
var _ = (chan int)(nil) == (chan bool)(nil) // error
下面的代码可以编译
type IntPtr *int
// 他俩底层的类型都是IntPtr*
var _ = IntPtr(nil) == (*int)(nil)
// go中每一个interface{}可以表示所有
var _ = (interface{})(nil) == (*int)(nil)
// 当向通道可以转换为单向通道,并且他俩底层的数据类型一致
var _ = (chan int)(nil) == (chan<- int)(nil)
var _ = (chan int)(nil) == (<-chan int)(nil)
在 Go 中,映射、切片和函数类型不支持比较。比较无法比较的类型的两个值(包括空值)是非法的。 这是语法限制,其实和nil值感觉不是很大。
下列比较无法编译。
var _ = ([]int)(nil) == ([]int)(nil)
var _ = (map[string]int)(nil) == (map[string]int)(nil)
var _ = (func())(nil) == (func())(nil)
下面是ok的
var _ = ([]int)(nil) == nil
var _ = (map[string]int)(nil) == nil
var _ = (func())(nil) == nil
如果比较的nil中有一个是接口值,结果总等于false,因为他们的底层的类型不一致,编译器不能推断出来,我们知道go的接口的底层有一对数据(类型和实际的值。)对于下面的例子中
interface{}(nil) 的一对数据为(type=nil,value=nil)
(*int)(nil) )的一对数据为 (type=*int,value=nil)
fmt.Println( (interface{})(nil) == (*int)(nil) ) // false
从空映射值中检索元素将始终返回零元素值。
fmt.Println( (map[string]int)(nil)["key"] ) // 0
fmt.Println( (map[int]bool)(nil)[123] ) // false
fmt.Println( (map[int]*int64)(nil)[123] ) //
遍历map和slices类型的nil的步长为0
遍历数组指针的步长是它对应的数组的长度
遍历一个nil的channel会堵塞当前线程
代码:
for range []int(nil) {
fmt.Println("Hello")
} // 不会输出
for range map[string]string(nil) {
fmt.Println("world")
} // 不会出输出
for i := range (*[5]int)(nil) {
fmt.Println(i)
} // 输出0,1,2,3,4
for range chan bool(nil) { // 堵塞在这里,被死锁检测到。
fmt.Println("Bye")
}
代码:
package main
type Slice []bool
func (s Slice) Length() int {
return len(s)
}
func (s Slice) Modify(i int, x bool) {
s[i] = x // panic if s is nil
}
func (p *Slice) DoNothing() {
}
func (p *Slice) Append(x bool) {
*p = append(*p, x) // panic if p is nil
}
func main() {
// 下面的不会抛错
_ = ((Slice)(nil)).Length
_ = ((Slice)(nil)).Modify
_ = ((*Slice)(nil)).DoNothing
_ = ((*Slice)(nil)).Append
_ = ((Slice)(nil)).Length()
((*Slice)(nil)).DoNothing()
// 下面的会报错,但是报错不是在调用方法的时候,而是在方法里面操作对象的时候报错
/*
((Slice)(nil)).Modify(0, true)
((*Slice)(nil)).Append(true)
*/
}
package main
import "fmt"
func main() {
fmt.Println(*new(*int) == nil) // true
fmt.Println(*new([]int) == nil) // true
fmt.Println(*new(map[int]bool) == nil) // true
fmt.Println(*new(chan string) == nil) // true
fmt.Println(*new(func()) == nil) // true
fmt.Println(*new(interface{}) == nil) // true
}
在 Go 中,为了简单和方便,nil 被设计为一个标识符,可以用来表示某些类型的零值。他不是一个不变的值,而是随着不同类型有不同的类型,在日常的编码中,编译器会根据上下文来推导出。
首先代码中出现问题的是interface{},按照上面说的「两个nil值可能不相等」来说,结果已经推出来了。在go中接口是一堆方法的集合,并且接口变量的底层是一对数据结构(类型,实际的值),断言,反射是来拿到它们在做处理,先看下面的代码
var p *int // (type=*int,value=nil)
var i interface{} // (type=nil,value=nil)
if i != p { // (type=*int,value=nil) != (type=nil,value=nil)
// to successfully compare these values, both type and value must match
fmt.Println("not a nil")
}
// 运行结果是 "not a nil"
这两虽然都是nil,但他们的类型不一致。
在看一段代码
var p *int // (type=*int,value=nil)
var i interface{} // (type=nil,value=nil)
if p != nil{ // (type=*int,value=nil) != (type=*int,value=nil)
fmt.Println("not a nil p")
}
if i != nil { // (type=nil,value=nil) != (type=nil,value=nil)
fmt.Println("not a nil i1")
}
i = p // assign p to i
// a hardcoded nil is always nil,nil (type,value)
if i != nil { // (type=*int,value=nil) != (type=nil,value=nil)
fmt.Println("not a nil i2")
}
// 运行结果是 "not a nil i"
类比一开始的代码,结合上面的分析,看下面的解释。
### 如何处理这种情况
有三种方式,代码如下
import "reflect"
import "unsafe"
func do(v interface{}){
// 可能有问题,因为这是接口类型的变量
// 比如传递进来的是 (type=*int,value=nil),永远不会为true,这里的nil是 (type=nil,value=nil)
if v == nil {
}
// 这种方式是ok的,将nil值转换为同一类型下来比较
// 但是这样的比较要对业务很熟悉,实际动起手来比较麻烦
if v == (*int)(nil){
}
// 因为是nil,所以可以拿到 反射中的Value是否为nil
if reflect.ValueOf(v).IsNil() {
}
// 通过unsafe来直接检查一个接口类型的变量的value部分是否为0,但是如果value不是可以为nil的那5中类型的话也不会报错
if (*[2]uintptr)(unsafe.Pointer(&v))[1] == 0 {
}
}
对于上面的代码,可以采取下面的方式来判断
相关博客:
Why Golang Nil Is Not Always Nil? Nil Explained