golang问题总结

xiaolei1982 · · 538 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

//author: ysqi ,https://yushuangqi.com

package main

import (
	"fmt"
)

func sliceModify(slice []int) {
	// slice[0] = 88
	slice[0] = 1000
	slice = append(slice, 6)
}

func modify(array []int) {
	array[0] = 10
	fmt.Println("In modify(), array values:", array)
}

func main() {
	slice := []int{1, 2, 3, 4, 5}
	sliceModify(slice)
	fmt.Println(slice)

	array := []int{1, 2, 3, 4, 5}
	modify(array)
	fmt.Println("In main(), array values:", array)
}


[1000 2 3 4 5]
In modify(), array values: [10 2 3 4 5]

 

In main(), array values: [10 2 3 4 5]

 

go的函数传递除了map,slice,channel都是值类型传递,特别是数组不是和c一样是引用传递

其次slice的append操作如果超过了cap容量,就会生成新的slice

 

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {

	var t = make([]int, 0, 10)
	var s = make([]int, 0, 10) // 到此为止, t,s 都分配了底层数组,cap为10,只是t,s指定了len为0,fmt.Print时才没显示出内容来

	fmt.Printf("addr:%p \t\tlen:%v content:%v\n", t, len(t), t) // 这里的%p打印的其实是slice底层数组的首地址
	fmt.Printf("addr:%p \t\tlen:%v content:%v\n", s, len(s), s)

	t = append(s, 1, 2, 3, 4) // s的底层数组变化,append返回新的描述struct,假设是tmp,tmp的len则为4,并指向了s的底层数组,再用tmp覆盖t,所以下面两行fmt.Printf打印的%p一样

	fmt.Println(t)
	fmt.Println(s)

	fmt.Printf("addr:%p \t\tlen:%v content:%v\n", t, len(t), t) // t的len为4
	fmt.Printf("addr:%p \t\tlen:%v content:%v\n", s, len(s), s) // s的len为0,因为t,s的len不一样,内容才不同

	fmt.Println("---- 辅助代码 -----")
	sliceHeaderT := (*reflect.SliceHeader)((unsafe.Pointer(&t)))
	sliceHeaderS := (*reflect.SliceHeader)((unsafe.Pointer(&s)))
	fmt.Printf("sliceHeaderT: %+v\n", sliceHeaderT) // Data字段的值其实和上面你打印的地址是同一个,自己可以去换算一下
	fmt.Printf("sliceHeaderS: %+v\n", sliceHeaderS)

	fmt.Printf("addr:%p \t\tlen:%v content:%v\n", &t, len(t), t) // t的真实地址,明显和你上面打印的不同
	fmt.Printf("addr:%p \t\tlen:%v content:%v\n", &s, len(s), s)

	s = append(s, 5) // 修改s的底层数组,且len变为1

	fmt.Println(t) // 因为t,s 共享底层数组,所以t,s的首个元素都是5
	fmt.Println(s)
	fmt.Printf("sliceHeaderT: %+v\n", sliceHeaderT)
	fmt.Printf("sliceHeaderS: %+v\n", sliceHeaderS)

	sliceHeaderS.Len = 2
	fmt.Println(s)
}

addr:0xc420064000 len:0 content:[]
addr:0xc420064050 len:0 content:[]
[1 2 3 4]
[]
addr:0xc420064050 len:4 content:[1 2 3 4]
addr:0xc420064050 len:0 content:[]
---- 辅助代码 -----
sliceHeaderT: &{Data:842350870608 Len:4 Cap:10}
sliceHeaderS: &{Data:842350870608 Len:0 Cap:10}
addr:0xc42000a060 len:4 content:[1 2 3 4]
addr:0xc42000a080 len:0 content:[]
[5 2 3 4]
[5]
sliceHeaderT: &{Data:842350870608 Len:4 Cap:10}
sliceHeaderS: &{Data:842350870608 Len:1 Cap:10}

[5 2]

##############################################################################################

2 golang的闭包和匿名函数

func f(i int) func() int {
 return func() int {
 i++
 return i
 }
}

和js的闭包如出一辙,但是js的闭包原理没有研究过,golang的源码比较容易分析,但是应该是大差不差

首先要引用局部变量,该变量一定不能在堆栈上,必须分配到堆上才不至于释放,所以,

 

go 会生成对应的函数对象类型,大概这样
type foo struct {
fp uintptr
x *int
}

每次调用  都会 new 对象并且把函数指针和堆上(重新)分配的 x 的指针写入,返回对象而不是函数指针
执行的时候将函数对象也传给 fp 指向的函数 (比如通过寄存器)

 

3 golang多进程

1 golang没有如同c函数的fork函数,其多进程实现方案一般有两种, 
  cmd := exec.Command(os.Args[0], args...)
  process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
    Dir:   originalWD,
    Env:   env,
    Files: allFiles,
  })

  其中看过平滑启动的一些代码注意到fork+execv方式派生子进程方式
  又重新看了下nginx源码,nginx的平滑启动也采用该方式
  c代码的fork+exevc派生的子进程会继承父进程打开的socket句柄
  但是golang派生子进程需要通过传递属性才会继承所以有了下面代码
  env = append(env, fmt.Sprintf("%s%d", envCountKeyPrefix, len(listeners)))

  allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...)
  process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
    Dir:   originalWD,
    Env:   env,
    Files: allFiles,
  })

  其中files是打开的文件句柄
golang默认不传递,但是c的镜像进程方式会传递,  这里面牵扯一个问题,
close_on_exec 是一个进程所有文件描述符(文件句柄)的位图标志,每个比特位代表一个打开的文件描述符,用于确定在调用系统调用execve()时需要关闭的文件句柄(参见include/fcntl.h)。当一个程序使用fork()函数创建了一个子进程时,

通常会在该子进程中调用execve()函数加载执行另一个新程序。此时子进程将完全被新程序替换掉,并在子进程中开始执行新程序。

若一个文件描述符在close_on_exec中的对应比特位被设置,那么在执行execve()时该描述符将被关闭,否则该描述符将始终处于打开状态。

所以默认excev会保持打开,如果用此标志位会默认关闭

通过以上发现,真的是环环相扣有点意思

go test -v -bench . -benchmem

go test -race  竞争检测

go build -gcflags=-m -o test 内联打印并且能准确分析程序的变量分配位置(escape),

开启逃逸分析日志很简单,只要在编译的时候加上-gcflags '-m',但是我们为了不让编译时自动内连函数,一般会加-l参数,最终为-gcflags '-m -l'

go tool objdump -s "main.main" test 汇编打印

 

go tool pprof mysql.test cpu.prof  火焰图

/usr/local/Cellar/go/1.8.1/libexec/bin/go build -o wine -gcflags "-N -l -m" &&  GODEBUG="gctrace=1,scheddetail=1,schedtrace=1000"   ./wine 打印垃圾回收以及响应时间等

 

6

package main
import "fmt"
func main() {  
    ch := make(chan string)
    go func() {
        for m := range ch {
            fmt.Println("processed:",m)
        }
    }()
    ch <- "cmd.1"
    ch <- "cmd.2" //won't be processed
}

cmd.2未必执行是因为,ch接受者已经准备好了后,ch <- "cmd.2"立马返回,主进程结束所以不能执行

管道可以用for  ... range遍历,直到close(ch)返回,否则继续阻塞

import "time"

type bb struct{
 a int
 b string
}

func main() {
    ch := make(chan bb)
    go func() {
    for  a := range ch{
            fmt.Printf("processed:%d\n",a.b)
    }}()
cc:=bb{1,"222"}
ch <-cc
close(ch)
time.Sleep(time.Duration(3)*time.Second)

如果close(ch)是可以通过<-ch监听关闭信号,判断管道关闭用v,ok  : <-ch 中的ok判定

如果关闭ch,v的值根据管道的类型返回,整形是0,字符串是空,结构体是空结构体


7.

// InterfaceStructure 定义了一个interface{}的内部结构
type InterfaceStructure struct {
    pt uintptr // 到值类型的指针
    pv uintptr // 到值内容的指针
}

如下接口类型interface包含一个类型一个值,所以当给接口变量赋值时候,即使对方变量是null,接口类型仍然有值,

这个之后接口与nil比较会失败,最好用err去判断

func main() {
	var data *byte
	var in interface{}

	fmt.Println(data, data == nil)	// <nil> true
	fmt.Println(in, in == nil)	// <nil> true

	in = data
	fmt.Println(in, in == nil)	// <nil> false	// data 值为 nil,但 in 值不为 nil
}

 

8  如果 map 一个字段的值是 struct 类型,则无法直接更新该 struct 的单个字段

因为map是有cap属性的,也就是会根据元素大小修改内存位置,所以你无法调用里面struct的值,

1
2
3
4
5
6
7
8
9
10
11
// 无法直接更新 struct 的字段值
type data struct {
	name string
}

func main() {
	m := map[string]data{
		"x": {"Tom"},
	}
	m["x"].name = "Jerry"
}

cannot assign to struct field m[“x”].name in map

 

9. 虽然有使用sync.Mutex做写锁,但是map是并发读写不安全的。map属于引用类型,并发读写时多个协程见是通过指针访问同一个地址,即访问共享变量,此时同时读写资源存在竞争关系。会报错误信息:“fatal error: concurrent map read and map write”。

报错原因:

写的时候设置了h.flags,获取的时候检查报错

写完会清除标志位

 

10. 


 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26


 

package main

import (

"fmt"

)

type People interface {

Speak(string) string

}

type Stduent struct{}

func (stu *Stduent) Speak(think string) (talk string) {

if think == "bitch" {

talk = "You are a good boy"

} else {

talk = "hi"

}

return

}

func main() {

var peo People = Stduent{}

think := "bitch"

fmt.Println(peo.Speak(think))

}

考题中的 func (stu *Stduent) Speak(think string) (talk string) 是表示结构类型*Student的指针有提供该方法,但该方法并不属于结构类型Student的方法。因为struct是值类型。

修改方法:

  • 定义为指针 go var peo People = &Stduent{}
  • 方法定义在值类型上,指针类型本身是包含值类型的方法。 go func (stu Stduent) Speak(think string) (talk string) { //... }

同时应该注意结构指针的方法和结构指针方法的区别,receive是不同的

型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集 
这条规则说的是如果我们用来调用特定接口方法的接口变量是一个指针类型,那么方法的接受者可以是值类型也可以是指针类型.

类型 T 的可调用方法集包含接受者为 T 的所有方法

 

 

 


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:xiaolei1982

查看原文:golang问题总结

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

538 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传