和作用域有关的一个例子
package main
import (
"errors"
"fmt"
)
type Teacher struct {
}
func (t *Teacher) ShowA() (int, error) {
var err error
a, err := 100, errors.New("test")
return a, err
}
func (t *Teacher) ShowB() (b int, err error) {
a, err := 100, errors.New("test")
b = a
return
}
func (t *Teacher) ShowC() (int, error) {
var err error
var b int
{
a, err := 100, errors.New("test")
_ = err
b = a
}
return b, err
}
func main() {
t := Teacher{}
a, err := t.ShowA()
fmt.Println(a, err)
b, err := t.ShowB()
fmt.Println(b, err)
c, err := t.ShowC()
fmt.Println(c, err)
}
输出:
100 test
100 test
100 <nil>
defer的执行次序(先进后出)和执行的点(return之前, 返回值赋值之后)。
package main
import (
"fmt"
)
func main() {
defer_call()
}
func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()
panic("触发异常")
}
输出:
打印后
打印中
打印前
panic: 触发异常
defer函数属延迟执行,延迟到调用者函数执行 return 命令前被执行。多个defer之间按LIFO先进后出顺序执行。
Panic触发时结束函数运行,在return前先依次打印:打印后、打印中、打印前 。最后由runtime运行时抛出打印panic异常信息。
函数的return value 不是原子操作.而是在编译器中分解为两部分:返回值赋值 和 return 。而defer刚好被插入到末尾的return前执行。故可以在derfer函数中修改返回值。比如下面:
//------------------------------------------------------
package main
import (
"fmt"
)
func main() {
fmt.Println(doubleScore(0)) //0
fmt.Println(doubleScore(20.0)) //40
fmt.Println(doubleScore(50.0)) //50
}
func doubleScore(source float32) (score float32) {
defer func() {
if score < 1 || score >= 100 {
//将影响返回值
score = source
}
}()
score = source * 2
return
//或者
//return source * 2
}
//---------------------------------------------------------
另一个例子
package main
import "fmt"
func calc(index string, a int) int {
fmt.Println("index:", index, "; a:", a)
return a * 2
}
func main() {
a := 10
defer calc("1", calc("2", a))
a = 20
defer calc("3", calc("4", a))
}
输出:
index: 2 ; a: 10
index: 4 ; a: 20
index: 3 ; a: 40
index: 1 ; a: 20
package main
func main() {
println(DeferFunc1(1))
println(DeferFunc2(1))
println(DeferFunc3(1))
}
func DeferFunc1(i int) (t int) {
t = i
defer func() {
t += 3
}()
return t
}
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
输出:
4
1
3
defer需要在函数结束前执行。 函数返回值名字会在函数起始处被初始化为对应类型的零值并且作用域为整个函数 DeferFunc1有函数返回值t作用域为整个函数,在return之前defer会被执行,所以t会被修改,返回4; DeferFunc2函数中t的作用域为函数,返回1; DeferFunc3返回3
注意for i, val := range slice中val是同一个对象,遍历过程中拷贝成被遍历的元素而已
package main
import (
"fmt"
)
type student struct {
Name string
Age int
}
func pase_student() map[string]*student {
m := make(map[string]*student)
stus := []student{
{Name: "zhou", Age: 24},
{Name: "li", Age: 23},
{Name: "wang", Age: 22},
}
for _, stu := range stus {
m[stu.Name] = &stu
//正确做法 m[stu.Name] = &stus[index] (遍历中加入index)
}
return m
}
func main() {
students := pase_student()
for k, v := range students {
fmt.Printf("key=%s,value=%v \n", k, v)
}
}
输出
输出的均是相同的值:&{wang 22}
类似的问题,结合调度器
package main
import (
"fmt"
"runtime"
"sync"
)
func init() {
fmt.Println("Current Go Version:", runtime.Version())
}
func main() {
runtime.GOMAXPROCS(1)
count := 10
wg := sync.WaitGroup{}
wg.Add(count * 2)
for i := 0; i < count; i++ {
go func() {
fmt.Printf("[%d]\n", i)
wg.Done()
}()
}
for i := 0; i < count; i++ {
go func(i int) {
fmt.Printf("-%d- \n", i)
wg.Done()
}(i)
}
wg.Wait()
}
输出: 产生这个结果在第一个循环中实际是随机的,也是遍历拷贝产生的
为什么 -9- 会第一个输出:??? #todo 也许和调度器相关 一种说法说:多goroutine的执行顺序并不保证。
Current Go Version: go1.8.3
-9-
[10]
[10]
[10]
[10]
[10]
[10]
[10]
[10]
[10]
[10]
-0-
-1-
-2-
-3-
-4-
-5-
-6-
-7-
-8-
Go中没有继承, 只能组合
https://zhuanlan.zhihu.com/p/27652856
interface里面重要的有两个东西{tab *itab(包含type,fun..); data unsafe.Pointer}
package main
import "fmt"
type People struct{}
func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("showB")
}
type Teacher struct {
People
}
func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}
func main() {
t := Teacher{}
t.ShowA()
}
输出:
showA # teacher没有showA 所以调用组合中的People
showB # 还是people
//------------------------------------------------------
如果改成这样
package main
import "fmt"
type People struct{}
func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}
type Teacher struct {
People
}
func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}
func main() {
t := Teacher{}
t.ShowA()
}
直接报错: p.ShowB undefined (type *People has no field or method ShowB)
在Go中只有值传递(包括接口类型),与具体的类型实现无关,但是某些类型具有引用的属性。测试代码, 参考
law of reflection
package main
import "fmt"
import "runtime"
func main() {
runtime.GOMAXPROCS(1)
int_chan := make(chan int, 1)
string_chan := make(chan string, 1)
int_chan <- 1
string_chan <- "hello"
select {
case value := <-int_chan:
fmt.Println("int_chan value:", value)
case value := <-string_chan:
panic("string chan error," + value)
}
}
输出:
随机触发异常,因为:
select 中只要有一个case能return,则立刻执行。
当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。
如果没有一个case能return则可以执行”default”块。
http://legendtkl.com/2017/07/30/understanding-golang-channel/
https://zhuanlan.zhihu.com/p/27917262
https://my.oschina.net/renhc/blog/2246871
http://legendtkl.com/2017/08/06/golang-channel-implement/
本质就是消息队列 有{qcount,dataqsiz,buf(存放数据的环形数组),closed,elemtype,lock mutex..}
GOMAXPROCS sets the maximum number of CPUs that can be executing simultaneously and returns the previous setting. If n < 1, it does not change the current setting. The number of logical CPUs on the local machine can be queried with NumCPU. This call will go away when the scheduler improves.
自 Go 1.5开始, Go的GOMAXPROCS默认值已经设置为 CPU的核数
结构
调度
为何要维护多个上下文P?因为当一个OS线程被阻塞时,P可以转而投奔另一个OS线程!
图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行所以的context P。
struct G
{
uintptr stackguard; // 分段栈的可用空间下界
uintptr stackbase; // 分段栈的栈基址
Gobuf sched; //进程切换时,利用sched域来保存上下文
uintptr stack0;
FuncVal* fnstart; // goroutine运行的函数
void* param; // 用于传递参数,睡眠时其它goroutine设置param,唤醒时此goroutine可以获取
int16 status; // 状态Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead
int64 goid; // goroutine的id号
G* schedlink;
M* m; // for debuggers, but offset not hard-coded
M* lockedm; // G被锁定只能在这个m上运行
uintptr gopc; // 创建这个goroutine的go表达式的pc
...
};
struct M
{
G* g0; // 带有调度栈的goroutine
G* gsignal; // signal-handling G 处理信号的goroutine
void (*mstartfn)(void);
G* curg; // M中当前运行的goroutine
P* p; // 关联P以执行Go代码 (如果没有执行Go代码则P为nil)
P* nextp;
int32 id;
int32 mallocing; //状态
int32 throwing;
int32 gcing;
int32 locks;
int32 helpgc; //不为0表示此m在做帮忙gc。helpgc等于n只是一个编号
bool blockingsyscall;
bool spinning;
Note park;
M* alllink; // 这个域用于链接allm
M* schedlink;
MCache *mcache;
G* lockedg;
M* nextwaitm; // next M waiting for lock
GCStats gcstats;
...
};
struct P
{
Lock;
uint32 status; // Pidle或Prunning等
P* link;
uint32 schedtick; // 每次调度时将它加一
M* m; // 链接到它关联的M (nil if idle)
MCache* mcache;
G* runq[256];
int32 runqhead;
int32 runqtail;
// Available G's (status == Gdead)
G* gfree;
int32 gfreecnt;
byte pad[64];
};
https://www.cnblogs.com/zkweb/p/7815600.html
https://tonybai.com/2017/06/23/an-intro-about-goroutine-scheduler/
sysmon主要完成如下工作
http://legendtkl.com/2017/04/28/golang-gc/
https://www.jianshu.com/p/9c8e56314164
使用mark-and-swap方式
从MCache中分配,并且定期做垃圾回收,将线程的MCache中的空闲内存返回给全局控制堆。小于32K为小对象,大对象直接从全局控制堆上以页(4k)为单位进行分配,也就是说大对象总是以页对齐的。一个页可以存入一些相同大小的小对象,小对象从本地内存链表中分配,大对象从中心内存堆中分配。
大约有100种内存块类别,每一类别都有自己对象的空闲链表。小于32kB的内存分配被向上取整到对应的尺寸类别,从相应的空闲链表中分配。一页内存只可以被分裂成同一种尺寸类别的对象,然后由空闲链表分配器管理。
标记清扫算法分为两阶段:标记阶段和清扫阶段。
参考
package main
import "fmt"
func main() {
s := make([]int, 5, 6)
fmt.Println(cap(s), len(s))
s = append(s, 1, 2, 3)
fmt.Println(cap(s), len(s))
fmt.Println(s)
}
输出:
6 5
12 8 # cap扩大了一倍
[0 0 0 0 0 1 2 3]
//------------------------------------------------------------
package main
import "fmt"
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}
编译不过,应该改成: *list = append(*list, 1)
map, slice等都不是线程安全的
package main
import (
"fmt"
"sync"
)
type UserAges struct {
ages map[string]int
sync.Mutex
}
func (ua *UserAges) Add(name string, age int) {
ua.Lock()
defer ua.Unlock()
ua.ages[name] = age
}
func (ua *UserAges) Get(name string) int {
if age, ok := ua.ages[name]; ok {
return age
}
return -1
}
func main() {
count := 1000
gw := sync.WaitGroup{}
gw.Add(count * 3)
u := UserAges{ages: map[string]int{}}
add := func(i int) {
u.Add(fmt.Sprintf("user_%d", i), i)
gw.Done()
}
for i := 0; i < count; i++ {
go add(i)
go add(i)
}
for i := 0; i < count; i++ {
go func(i int) {
defer gw.Done()
u.Get(fmt.Sprintf("user_%d", i))
}(i)
}
gw.Wait()
fmt.Println("Done")
}
map并发读写都不安全的
可能panic, (限制GOMAXPROCS=1不会panic)
初始化流程为 import(递归) -> const -> var -> init函数
对象的方法和对象指针的方法是有区别的
package main
import "fmt"
type People struct {
A string
B string
}
func (t People) SetA(a string) {
t.A = a
fmt.Printf("infunc: %+v %p \n", t, &t)
}
func (t *People) SetB(b string) {
t.B = b
fmt.Printf("infunc: %+v %p %p\n", t, &t, t)
}
func main() {
t := People{}
t.SetA("a")
fmt.Printf("%+v %p \n", t, &t)
t.SetB("b")
fmt.Printf("%+v %p \n", t, &t)
}
输出: 由于调用会发生值拷贝,对象方法拷贝成本高,而且不能实际改变对象, 注意打印的几个指针地址为什么会是这样
infunc: {A:a B:} 0xc42000a180
{A: B:} 0xc42000a160
infunc: &{A: B:b} 0xc42000c030 0xc42000a160
{A: B:b} 0xc42000a160
package main
import "fmt"
func main() {
name := "ab好"
fmt.Printf("%d: %c, %c, %c\n", len(name), name[0], name[1], name[2])
for _, a := range name {
fmt.Println(a)
}
}
输出:
5: a, b, å
97
98
22909
interface 是type + val, 两者都是nil才是nil
func Foo(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}
func main() {
var x *int = nil
Foo(x)
}
输出:
not-empty interface
//-------------------------------------
package main
import "fmt"
func main() {
var data *byte
var in interface{}
fmt.Println(data,data == nil) //prints: <nil> true
fmt.Println(in,in == nil) //prints: <nil> true
in = data
fmt.Println(in,in == nil) //prints: <nil> false
//'data' is 'nil', but 'in' is not 'nil'
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。