前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang技术点整理

golang技术点整理

原创
作者头像
王磊-字节跳动
发布2019-12-03 12:00:00
1.3K0
发布2019-12-03 12:00:00
举报
文章被收录于专栏:01ZOO01ZOO

多重赋值

和作用域有关的一个例子

代码语言:txt
复制
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相关

defer的执行次序(先进后出)和执行的点(return之前, 返回值赋值之后)。

代码语言:txt
复制
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是同一个对象,遍历过程中拷贝成被遍历的元素而已

代码语言:txt
复制
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}

类似的问题,结合调度器

代码语言:txt
复制
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- 

interface相关

Go中没有继承, 只能组合

https://zhuanlan.zhihu.com/p/27652856

interface里面重要的有两个东西{tab *itab(包含type,fun..); data unsafe.Pointer}

代码语言:txt
复制
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中的值传递

在Go中只有值传递(包括接口类型),与具体的类型实现无关,但是某些类型具有引用的属性。测试代码, 参考

  • array传递会拷贝整块数据内存,传递长度为len(arr) * Sizeof(elem)
  • string、slice、interface传递的是其runtime的实现,所以长度是固定的,分别为16、24、16字节(amd64)
  • map、func、chan、pointer传递的是指针,所以长度固定为8字节(amd64)
  • struct传递的是所有字段的内存拷贝,所以长度是所有字段的长度和

reflect

官方参考

law of reflection

    1. Reflection goes from interface value to reflection object.
    • reflect.TypeOf and reflect.ValueOf, retrieve reflect.Type and reflect.Value pieces out of an interface value.
    • reflect.Type是个interface,体现go类型, reflect.value是struct,表明具体内容, 看这个index可以知道是什么作用 https://golang.org/pkg/reflect/#pkg-index
    1. Reflection goes from reflection object to interface value.
    • func (v Value) Interface() interface{};y := v.Interface().(float64); value可以恢复到interface
    • func (v Value) Type() Type; value中有type信息
    1. To modify a reflection object, the value must be settable.
    • v.SetFloat(7.1)
    • 如果是pointer有func (v Value) Elem() Value; returns the value that the interface v contains or that the pointer v points to

参考

unsafe

runtime

select 相关

代码语言:txt
复制
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”块。

channel 相关

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..}

goroutine 相关

参考

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的核数

结构

  • 结构体G 是对goroutine的抽象
  • 结构体M 是machine的缩写,是对机器的抽象,每个m都是对应到一条操作系统的物理线程。M必须关联了P才可以执行Go代码,但是当它处理阻塞或者系统调用中时,可以不需要关联P。
  • Go1.1中新加入的一个数据结构,它是Processor的缩写。结构体P的加入是为了提高Go程序的并发度,实现更好的调度。M代表OS线程。P代表Go代码执行时需要的资源。当M执行Go代码时,它需要关联一个P,当M为idle或者在系统调用中时,它也需要P。有刚好GOMAXPROCS个P。所有的P被组织为一个数组,在P上实现了工作流窃取的调度器。

调度

  • 找到一个等待运行的g
  • 如果g是锁定到某个M的,则让那个M运行
  • 否则,调用execute函数让g在当前的M中运行
image.png
image.png

为何要维护多个上下文P?因为当一个OS线程被阻塞时,P可以转而投奔另一个OS线程!

图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行所以的context P。

image
image
代码语言:txt
复制
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主要完成如下工作

  • 释放闲置超过5分钟的span物理内存;
  • 如果超过2分钟没有垃圾回收,强制执行;
  • 将长时间未处理的netpoll结果添加到任务队列;
  • 向长时间运行的G任务发出抢占调度;
  • 收回因syscall长时间阻塞的P;

内存管理和垃圾回收

http://legendtkl.com/2017/04/28/golang-gc/

https://www.jianshu.com/p/9c8e56314164

使用mark-and-swap方式

从MCache中分配,并且定期做垃圾回收,将线程的MCache中的空闲内存返回给全局控制堆。小于32K为小对象,大对象直接从全局控制堆上以页(4k)为单位进行分配,也就是说大对象总是以页对齐的。一个页可以存入一些相同大小的小对象,小对象从本地内存链表中分配,大对象从中心内存堆中分配。

大约有100种内存块类别,每一类别都有自己对象的空闲链表。小于32kB的内存分配被向上取整到对应的尺寸类别,从相应的空闲链表中分配。一页内存只可以被分裂成同一种尺寸类别的对象,然后由空闲链表分配器管理。

标记清扫算法分为两阶段:标记阶段和清扫阶段。

slice/map 相关 make/new 相关

参考

代码语言:txt
复制
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等都不是线程安全的

代码语言:txt
复制
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初始化问题

参考

初始化流程为 import(递归) -> const -> var -> init函数

对象方法问题

对象的方法和对象指针的方法是有区别的

代码语言:txt
复制
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 

rune

代码语言:txt
复制
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 和 nil的问题

interface 是type + val, 两者都是nil才是nil

代码语言:txt
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多重赋值
  • defer相关
  • 遍历相关
  • interface相关
    • go中的值传递
    • reflect
    • unsafe
    • runtime
    • select 相关
    • channel 相关
    • goroutine 相关
      • 抢占调度
      • 内存管理和垃圾回收
      • slice/map 相关 make/new 相关
      • 线程安全相关
      • import初始化问题
      • 对象方法问题
      • rune
      • interface 和 nil的问题
      相关产品与服务
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档