go 底层原理及基本概念

文章目录

    • 1. go底层运行过程
      • 1.1 真正的入口
      • 1.2 如何查看汇编文件
      • 1.3 运行流程梳理
    • 2. go 面向对象?
      • 2.1 Is Go an object-oriented language?
      • 2.2 go 中 "class"
      • 2.3 go 中 "继承"
      • 2.3 go 中 接口
    • 3. go中什么变量0字节
      • 3.1 基本类型查看占用字节数
      • 3.2 占用0字节的类型----空结构体
      • 3.3 空结构体用途

1. go底层运行过程

不会有人还以为程序运行的入门是main 函数吧。其实很多语言在main函数之前就已经做过很多事情了,下面咋们就分析分析go中的入门到执行的整个过程吧~~~

1.1 真正的入口

实际上是runtime/rt0_xxx.s 这个文件中, 有关runtime的叙述在上一节已经加以说明
比如在windows 平台上就是在runtime包中rt0_windows_amd64.s 这个汇编文件中,至于为什么编写成汇编语言,那肯定的汇编语言效率高呀~
go 底层原理及基本概念_第1张图片
这个的命令就是对应不同平台的,amd64 、386、arm实际上就是对应不同的芯片架构,amd64机器和inter64位机器都用一个文件,这里都是64位机器(业界习惯将x86-64架构说成amd64)

查看里面的接口,64架构的都是调用的同一个接口
go 底层原理及基本概念_第2张图片

1.2 如何查看汇编文件

我这里使用的IDE 是Goland,查看源码非常方便

比如我需要找rt0_amd64 有关的文件,因为是汇编文件,需要点击里面的Files, 这个窗口需要双击shitf 出来
go 底层原理及基本概念_第3张图片
之后跳转到这个汇编函数中,这里的意思就是将argc, argv 变量放入到寄存器中
go 底层原理及基本概念_第4张图片
然后就跳转到rt0_go 这个函数中,这个比较核心了

  1. 先将argc,argv 参数放到栈上
  2. 初始化一个g0 的协程, 不是作为程序的第一个协程
  3. 类型检查啥的
  4. 在执行一个go中的方法, runtime.check(),运行时检测
  5. 将argc, argv 拷贝到go 中的代码上去
  6. runtime.osinit () 判断系统的字长,
  7. 调度器初始化 runtime.scheldinit()
  8. 取主函数的地址mainPC, 是runtime.main的地址, 然后new 一个协程
  9. runtime.main 中doinit --> 垃圾回收器—>main_main函数(就是用户的main包中的main方法)
    go 底层原理及基本概念_第5张图片

1.3 运行流程梳理

  1. 跳入汇编函数 rt0_go 中
  2. 读取参数数量argc、argv 到栈上
  3. 初始化g0执行栈 (1. g0 是为了调度协程而产生的协程(母协程) 2. g0是每个go程序的第一个协程)
  4. 运行时检测check函数, runtime.check()
    里面包含:
    1) 检查各种类型的长度
    2) 检查指针操作
    3) 检查结构体字段的偏移量
    4) 检查atomic原子操作
    5) 检查CAS操作
    6.)检查栈大小是否是2的幂次
  5. 参数初始化runtime.args
    1)对命令行中的参数进行处理
    2)参数数量赋值给argc int32
    3)参数值复制给argv **byte
  6. 调度器初始化 runtime.schedinit
    1)全局栈空间内存分配
    2)加载命令行参数到os.Args
    3)堆内存空间的初始化
    4)加载操作系统环境变量
    5)初始化当前系统线程
    6)垃圾回收的参数初始化
    7)算法初始化(map、hash)
    8)设置process 数量
  7. 创建主协程(现在就有两个协程了)
    1)创建一个新的协程,执行runtime.main (这个是runtime中main,和用户写的main函数不是同一个)
    2) 放入 调度器等待调度
  8. 初始化M
    初始化一个M,用来调度主协程的(后面在深入)
  9. 主协程执行主函数
    1)执行runtime 包中的init方法
    2) 启动GC 垃圾收集器
    3)执行用户包依赖的init方法
    4)执行用户主函数main.main() , 在这里就到了我们写的main 函数了

2. go 面向对象?

2.1 Is Go an object-oriented language?

可以说是, 也可以说不是
go 底层原理及基本概念_第6张图片

  1. go 允许OO(面向对象)的编程风格
  2. go 的struct 可以看作其它语言的class
  3. go 缺乏其他语言的继承结构的(那个叫组合)
  4. go 的接口与其他语言有很大差异

2.2 go 中 “class”

  1. go 中用struct 表示一类数据
  2. struct 每个实例并不是对象,而是此类型的值
  3. struct 也可以定义方法

2.3 go 中 “继承”

  1. go 并没有继承关系, 下面的例子其实是组合
type People struct {
	name string
	age int
} 
type Man struct {
	People
}
  1. 所谓go 的继承只是组合而己
type People struct {
	name string
	age int
} 
type Man struct {
	People	
} 
func (p People) walk() {
} 
func main() {
	m := Man{}
	m.walk() // 这其实是一个语法糖, 真正底层是	通过下面的方式实现的
	m.People.walk()
	fmt.Println("nihao")
}
  1. 组合中的匿名字段,通过语法糖达成了类似继承的效果
    像上面的只有类型没有名字的 就叫做匿名字段, Man 结构体中的People 例子

2.3 go 中 接口

  1. 接口可以定义一组行为相似的struct
  2. struct 并不是显式实现接口,而是隐式实现

go 底层原理及基本概念_第7张图片
在 People 结构体实现函数的一瞬间,编译器就能提示哪些类实现了接口,该类中实现了哪些接口

3. go中什么变量0字节

3.1 基本类型查看占用字节数

int: 大小跟随着系统字长(因此才有int32, int64这类型出来)

package main
import (
	"fmt"
	"unsafe"
) 
func main() {
	fmt.Println(unsafe.Sizeof(int(1)))
}
// int    64位系统占8字节,32位系统占4字节 根据系统字长决定, 
// int64  永远占8字节

指针: 指针的大小也是随着系统字长决定
在64位系统中,是地址总线和数据总线是64位

package main
import (
	"fmt"
	"unsafe"
) 
func main() {
	i := int(0)
	p := &i
	fmt.Println(unsafe.Sizeof(p))
}

其他基本类型也可以按照上面查询占用的字节数

3.2 占用0字节的类型----空结构体

  1. 单个空结构体是有地址,没有大小的 (地址被称为 zerobase)
package main
import (
	"fmt"
	"unsafe"
) 
type K struct {
} 
func main() {
	a := K{}
	fmt.Println(unsafe.Sizeof(a))
	fmt.Printf("%p\n", &a) // 有地址没有长度
	// 0
	// 0xebc578   : 这是一个空结构体指向的地址,后面你会发现这个是固定的
}
  1. 中间含有其他类型,但空结构体独立出现时(不被包含在其他结构体中)
    空结构体的地址均相同,均指向一个地址zerobase
package main
import (
	"fmt"
	"unsafe"
) 
type K struct {
} 
func main() {
	a := K{}
	b := int(0)
	c := K{}
	fmt.Println(unsafe.Sizeof(a))
	fmt.Printf("%p\n", &a) // 有地址没有长度
	fmt.Printf("%p\n", &b)
	fmt.Printf("%p\n", &c)
	//0
	//0xbdc578
	//0xc00000a0c0
	//0xbdc578
}

我们看一下zerobase 的类型会发现是 uintptr
go 底层原理及基本概念_第8张图片

  1. 空结构体不是单独的出现时, 会出现这样的情况,根据依赖的结构体地址走
package main
import (
	"fmt"
) 
type K struct {
} 
type F struct {
	num1 K
	num2 int32
} 
func main() {
	f := F{}
	a := K{}
	//fmt.Println(unsafe.Sizeof())
	fmt.Printf("%p\n", &f.num1)
	fmt.Printf("%p\n", &f.num2)
	fmt.Printf("%p\n", &a)
}

// 0xc00000a0c0
// 0xc00000a0c0
// 0x28c578

3.3 空结构体用途

  1. 结合map 实现hashmap
func main() {
	m := map[string]int{}
	n := map[string]struct{}{} // hashset // key :	null
	n["a"] = struct{}{} // 值不占任何空间
}
  1. 结合channel 当作纯信号
func main() {
a := make(chan struct{}) // 只想发送信号,不想携带任何信息,这样就不占任何内存
}

参考:
后台服务器:https://course.0voice.com/v1/course/intro?courseId=5&agentId=0

你可能感兴趣的:(【go专栏】从原理解析go语言,golang,开发语言,后端)