Go是一种编译型语言,结合了解释型语言的优点(如调试效率,跨平台),动态类型语言的优点(如开发效率),以及静态类型的优点(如安全性);
Go是静态类型语言,类型系统没有层级,因此无需在类型关系定义上耗费过多时间;
Go是垃圾回收型的语言,并为并发执行与通信提供了基本的支持;
Go目标是要成为一种现在的系统语言,不过目前更多用来构建支持高性能高并发web服务。
web server: /net/http
database: database/sql
compress: compress/gzip
cryptography: Crypto/md5,crypto/sha1
encoding: encoding/json
- 如何理解goroutine? 如何停止一个goroutine?
goroutine是Golang实现并发的最小逻辑单元;
非常轻量级,是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈;
是一种用户态线程,不被linux内核识别,运行在内核线程之上;
goroutine在cpu上换入换出,不断上下文切换的时候,必须要保证的事情就是保存现场和恢复现场。
可以通过设置信号channel来阻塞和退出一个goroutine。
- 如果gorouting中有很多阻塞系统调用,那是不是会创建很多内核线程?
是的,处于等待系统调用中M不会占用mcpu数量, 只有在运行中的M才会占用mcpu数量并对应到一条系统线程。
sysmon线程会将进入系统调用的M上的G队列移到其他M上等待执行。
P(最开始只有G和M,Processor可看作是后来加入的一种优化)用于解决之前并发执行中加锁的性能问题,是N:1调度器转到M:N调度器的重要部分。
1. Go中数组是值类型,数组赋值给另一个变量会对所有元素进行拷贝;
2. 向函数传递数组参数,传递的是该数组的拷贝而不是指针;
3. 数组的长度也是数组类型的一部分,[2]int和[4]int是两种不同的类型。
接口可以看作是一种对象的行为集合,一系列函数的集合;
要实现一个接口,需要实现该接口中的所有方法;
实现接口是非侵入式的,去掉复杂继承体系。
interface具体数据结构:
struct Eface
{
Type* type;
void* data;
};
struct Iface
{
Itab* tab;
void* data;
};
普通类型转接口类型是隐式的,接口类型转普通类型需要进行类型断言;
类型断言前一般要做些判断,避免panic: value,ok := a.(string)
闭包:函数+引用变量, 引用变量是不能在栈上分配的;
逃逸分析:go编译器可以分析出变量的作用范围,自动决定是在栈还是对分配内存,将闭包环境变量在堆上分配是Go实现闭包的基础;
结构体:返回闭包并不仅仅是返回一个函数,而是返回一个结构体,包含函数返回地址和所引用环境的变量地址等信息。
分段栈:
Go1.4之前的栈管理技术,运行时给每个goroutine分配8K的初始化栈内存;
在每个go函数之前有个代码块,检查已分配栈空间已不足,
不足的话会调用morestack函数来分配一段新的内存作栈空间,
然后将包括上一个栈地址等信息的struct和lessmore函数写入栈底,
接着重启gorouine,从将原来栈空间用光的函数开始执行(这里称为stack split)。
等该函数执行返回时,通过lessstack函数查找栈底部struct来调整栈指针,
使得返回原来栈空间,同时将该新栈段释放掉。
连续栈:
Go1.4之后使用连续栈,同样在每个函数入口进行栈溢出检查,
只是在栈溢出时会申请一个两倍于当前栈空间的内存空间,
然后把当前旧栈拷贝到新栈,释放旧栈空间,
最后程序重启goroutine并从引起重新分配栈段的函数继续执行。
实现栈拷贝的关键:
1. 只有在栈上分配的指针才能指向栈上的地址;
2. 需要知道栈上哪部分是指针,因为移动栈时需要把所有的指针指向新的目标地址;
原来Go运行时很多是C写的,大量运行时调用没有指针信息可用,这也是Go runtime被大规模重构的主要原因。