高级
最后更新于:2022-04-02 04:55:44
1、使用指针接收方法的值的实例
只要值是可取址的,那在这个值上调用指针接收方法是没问题的。换句话说,在某些情况下,你不需要在有一个接收值的方法版本。
然而并不是所有的变量是可取址的。Map的元素就不是。通过interface引用的变量也不是。
错误代码:
~~~
package main
import "fmt"
type data struct {
name string
}
func (p *data) print() {
fmt.Println("name:", p.name)
}
type printer interface {
print()
}
func main() {
d1 := data{"one"}
d1.print() //ok
var in printer = data{"two"} //error
in.print()
m := map[string]data{"x": data{"three"}}
m["x"].print() //error
}
~~~
编译错误:
~~~
./main.go:20:6: cannot use data literal (type data) as type printer in assignment:
data does not implement printer (print method has pointer receiver)
./main.go:23:8: cannot call pointer method on m["x"]
./main.go:23:8: cannot take the address of m["x"]
~~~
2、更新Map的值
如果你有一个struct值的map,你无法更新单个的struct值。
~~~
package main
type data struct {
name string
}
func main() {
m := map[string]data{"x": {"one"}}
m["x"].name = "two" //error
}
~~~
编译错误:
~~~
./main.go:9:14: cannot assign to struct field m["x"].name in map
~~~
这个操作无效是因为map元素是无法取址的。
而让Go新手更加困惑的是slice元素是可以取址的。
~~~
package main
import "fmt"
type data struct {
name string
}
func main() {
one := data{"one"}
s := []data{one}
s[0].name = "two" //ok
fmt.Println(s) //prints: [{two}]
}
~~~
运行结果:
~~~
[{two}]
~~~
注意在不久之前,使用编译器之一(gccgo)是可以更新map的元素值的,但这一行为很快就被修复了 它也被认为是Go 1.3的潜在特性。在那时还不是要急需支持的,但依旧在todo list中。
第一个有效的方法是使用一个临时变量。
~~~
package main
import "fmt"
type data struct {
name string
}
func main() {
m := map[string]data{"x": {"one"}}
r := m["x"]
r.name = "two"
m["x"] = r
fmt.Printf("%v\n", m) //prints: map[x:{two}]
}
~~~
运行结果:
~~~
map[x:{two}]
~~~
另一个有效的方法是使用指针的map。
~~~
package main
import "fmt"
type data struct {
name string
}
func main() {
m := map[string]*data{"x": {"one"}}
m["x"].name = "two" //ok
fmt.Println(m["x"]) //prints: &{two}
}
~~~
运行结果:
~~~
&{two}
~~~
顺便说下,当你运行下面的代码时会发生什么?
~~~
package main
type data struct {
name string
}
func main() {
m := map[string]*data{"x": {"one"}}
m["z"].name = "what?" //???
}
~~~
运行错误:
~~~
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x104f9af]
~~~
3、"nil" Interfaces和"nil" Interfaces的值
这在Go中是第二最常见的技巧,因为interface虽然看起来像指针,但并不是指针。interface变量仅在类型和值为“nil”时才为“nil”。
interface的类型和值会根据用于创建对应interface变量的类型和值的变化而变化。当你检查一个interface变量是否等于“nil”时,这就会导致未预期的行为。
~~~
package main
import "fmt"
func main() {
var data *byte
var in interface{}
fmt.Println(data, data == nil) //prints: true
fmt.Println(in, in == nil) //prints: true
in = data
fmt.Println(in, in == nil) //prints: false
//'data' is 'nil', but 'in' is not 'nil'
}
~~~
运行结果:
~~~
true
true
false
~~~
当你的函数返回interface时,小心这个陷阱。
~~~
package main
import "fmt"
func main() {
doit := func(arg int) interface{} {
var result *struct{} = nil
if arg > 0 {
result = &struct{}{}
}
return result
}
if res := doit(-1); res != nil {
fmt.Println("good result:", res) //prints: good result:
//'res' is not 'nil', but its value is 'nil'
}
}
~~~
运行结果:
~~~
good result:
~~~
~~~
package main
import "fmt"
func main() {
doit := func(arg int) interface{} {
var result *struct{} = nil
if arg > 0 {
result = &struct{}{}
} else {
return nil
}
return result
}
if res := doit(-1); res != nil {
fmt.Println("good result:", res)
}
}
~~~
4、栈和堆变量
你并不总是知道变量是分配到栈还是堆上。在C++中,使用new创建的变量总是在堆上。在Go中,即使是使用new()或者make()函数来分配,变量的位置还是由编译器决定。编译器根据变量的大小和“泄露分析”的结果来决定其位置。这也意味着在局部变量上返回引用是没问题的,而这在C或者C++这样的语言中是不行的。
如果你想知道变量分配的位置,在“go build”或“go run”上传入“-m“ gc标志(即,go run -gcflags -m app.go)。
5、GOMAXPROCS, 并发, 和并行
默认情况下,Go仅使用一个执行上下文/OS线程(在当前的版本)。这个数量可以通过设置GOMAXPROCS来提高。
一个常见的误解是,GOMAXPROCS表示了CPU的数量,Go将使用这个数量来运行goroutine。而runtime.GOMAXPROCS()函数的文档让人更加的迷茫。GOMAXPROCS变量描述(https://golang.org/pkg/runtime/)所讨论OS线程的内容比较好。
你可以设置GOMAXPROCS的数量大于CPU的数量。
~~~
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.GOMAXPROCS(-1))
fmt.Println(runtime.NumCPU())
runtime.GOMAXPROCS(20)
fmt.Println(runtime.GOMAXPROCS(-1))
runtime.GOMAXPROCS(300)
fmt.Println(runtime.GOMAXPROCS(-1))
}
~~~
运行结果:
~~~
4
4
20
300
~~~
6、读写操作的重排顺序
Go可能会对某些操作进行重新排序,但它能保证在一个goroutine内的所有行为顺序是不变的。然而,它并不保证多goroutine的执行顺序。
package main
import (
"runtime"
"time"
)
var _ = runtime.GOMAXPROCS(3)
var a, b int
func u1() {
a = 1
b = 2
}
func u2() {
a = 3
b = 4
}
func p() {
println(a)
println(b)
}
func main() {
go u1()
go u2()
go p()
time.Sleep(1 * time.Second)
}
如果你多运行几次上面的代码,你可能会发现a和b变量有多个不同的组合:
a和b最有趣的组合式是"02"。这表明b在a之前更新了。
如果你需要在多goroutine内放置读写顺序的变化,你将需要使用channel,或者使用"sync"包构建合适的结构体。
7、优先调度
有可能会出现这种情况,一个无耻的goroutine阻止其他goroutine运行。当你有一个不让调度器运行的for循环时,这就会发生。
~~~
package main
import "fmt"
func main() {
done := false
go func() {
done = true
}()
for !done {
}
fmt.Println("done!")
}
~~~
for循环并不需要是空的。只要它包含了不会触发调度执行的代码,就会发生这种问题。
调度器会在GC、“go”声明、阻塞channel操作、阻塞系统调用和lock操作后运行。它也会在非内联函数调用后执行。
~~~
package main
import "fmt"
func main() {
done := false
go func() {
done = true
}()
for !done {
fmt.Println("not done!") //not inlined
}
fmt.Println("done!")
}
~~~
要想知道你在for循环中调用的函数是否是内联的,你可以在“go build”或“go run”时传入“-m” gc标志(如, go build -gcflags -m)。
另一个选择是显式的唤起调度器。你可以使用“runtime”包中的 Goshed()函数。
~~~
package main
import (
"fmt"
"runtime"
)
func main() {
done := false
go func() {
done = true
}()
for !done {
runtime.Gosched()
}
fmt.Println("done!")
}
~~~
';