go的goroutine调度机制:调度器,GMP(src/runtime/runtime.h源码)
G
代表一个goroutine对象,每次go调用的时候,都会创建一个G对象;
M
代表一个线程,每次创建一个M的时候,都会有一个底层线程创建;所有的G任务,最终还是在M上执行;
P
代表一个处理器,每一个运行的M都必须绑定一个P,就像线程必须在每一个CPU核上执行一样;
P的个数就是GOMAXPROCS(最大256),启动时是固定的,一般不修改;M的个数和P的个数不一定一样多(会有
休眠的M)(M最大10000);每个P保存着本地G任务队列,也有一个全局G任务队列;全局任务队列和各个本地G
任务队列按照一定策略相互交换(满了,则把本地队列的一半送给全局队列)
cgo
- go使用分段栈,初始分配很小,可动态增长。在进入函数时,插入检测指令实现。
- goroutine是协作式的,运行到调用runtime库时就有机会进行调度。
- cgo包含从go调用c,从c中调用go
注意:
C函数不是分段技术;
cgo函数不是协作式的;
C中不支持go的runtime;
数据结构与算法 知识脉络
学习方法:
知识点切碎 ====> 刻意联系 ======> 反馈(查看优秀方案、code review)
数组
数组实现
数组特性
限流中间件
实现方式
- 计数器法
- 滑动窗口
- 漏桶算法
- 令牌桶算法
漏桶算法
假设有有个容积大小固定的过滤器,过滤器的入口处流量大小不设限制,容器出口的流量大小固定;
过滤器
代表漏桶
过滤器入口
表示客户端连接过来的请求
过滤器出口
表示通过了限流中间件的客户端请求
因为过滤器容积固定,所以当出口流出的速率小于入口流入的速率时,随着时间的积累,过滤器的容积会被填满,此时再发送过来的请求就会被丢弃掉;达到限流的目的。
令牌桶
一个固定容积大小的容器,以恒定的速率向容器中放入令牌,直到桶内被放满令牌,桶内被令牌占满后,新加令牌会被丢弃;客户端每来一个请求,就被以请求对应包大小的比例,从容器内取出相应数量的令牌,当桶内令牌不足或为空时,请求将会被拒绝;
计数器法
在指定的时间区间内,为每个用户的请求累计计数,当累计计数超过设置的阈值时,拒绝请求;时间超过指定的时间区间时,自动对计数清零;
滑动窗口
设置一个固定长度的区间窗口,该窗口在时间轴上,从左向右进行滑动,统计窗口所在的时间区间内的累计请求数,超过累计计数时直接拒绝请求;
内置结构与方法
nil值
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
以上为go官方库,对nil的解释。
此处可以看到,nil前用的是var声明,说明nil在go中是值。
以上英文翻译为:
nil is a predeclared identifier representing the zero value for a pointer, channel, func, interface, map, or slice type.
nil是一个预先声明的标识符,表示指针、通道、函数、接口、映射或切片类型的零值。
Type must be a pointer, channel, func, interface, map, or slice type
类型必须是“切片”、“映射”、“接口”或“函数”类型。
close() 方法
// The close built-in function closes a channel, which must be either
// bidirectional or send-only. It should be executed only by the sender,
// never the receiver, and has the effect of shutting down the channel after
// the last sent value is received. After the last value has been received
// from a closed channel c, any receive from c will succeed without
// blocking, returning the zero value for the channel element. The form
// x, ok := <-c
// will also set ok to false for a closed channel.
func close(c chan<- Type)
close内置函数关闭一个channel,这个channel必须是双向或仅发送。
close()只能由发送方执行,永远不不要在接收方调用close(),并且在接收到最后发送的值后会关闭通道。在从关闭的信道c接收到最后一个值之后,任何来自c的接收都将成功而不会阻塞,返回信道元素的零值。
对于闭合通道,x,ok:=<-c
形式也将ok设置为false。
slice向头部增加元素
a = append(a, 0) // 切片扩展1个空间
copy(a[i+1:], a[i:]) // a[i:]向后移动1个位置
a[i] = x // 设置新添加的元素
x即为需要增加的元素;
array1:=[3]int{
1,2,3}
slice1:=array1[:]
fmt.Println(slice1)
slice1=append(slice1, 0)
fmt.Println(slice1)
copy(slice1[1:], slice1[0:])
fmt.Println(slice1)
slice1[0]=0
fmt.Println(slice1)
[1 2 3] [1 2 3 0] [1 1 2 3] [0 1 2 3]
copy函数
// The copy built-in function copies elements from a source slice into a
// destination slice. (As a special case, it also will copy bytes from a
// string to a slice of bytes.) The source and destination may overlap. Copy
// returns the number of elements copied, which will be the minimum of
// len(src) and len(dst).
func copy(dst, src []Type) int
copy内置函数将元素从源切片复制到目标切片。(作为一种特殊情况,它还将字节从一个字符串复制到一个字节片上。)源和目标可能重叠。Cop返回复制的元素数,最小值为len(src)和len(dst)。
切片内存泄漏
go语言中slice底层引用的是数组,如果底层的数组很大,而slice本身需要使用到数组的数据量很小,slice一直对这个大数组引用的话,就会导致gc无法释放这个底层大数组的内存空间,这样就会拖慢整个系统的运行效率。
例如:打开一个文件,读取到[]byte
,只是用一小部分数组数据作为slice在函数间传递,就会导致底层的这个文件数组一直不能被释放。合理的做法是,复制一份新的slice出来。
defer+匿名函数
多个defer的执行顺序为“后进先出”;
defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。
其中defer
语句延迟执行了一个匿名函数,因为这个匿名函数捕获了外部函数的局部变量v,这种函数我们一般叫闭包。闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问。
导包顺序
init执行顺序
同一个文件中的init函数,以出现的顺序依次执行;
sync/atomic 原子性操作
import (
"sync"
"sync/atomic"
)
var total uint64
func worker(wg *sync.WaitGroup) {
defer wg.Done()
var i uint64
for i = 0; i <= 100; i++ {
atomic.AddUint64(&total, i)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go worker(&wg)
go worker(&wg)
wg.Wait()
}
channel结构
channel是first-class(一等公民,一等),
可以存储到变量中,
可以作为参数传递给函数,
可以作为函数的返回值返回。
channel仅仅就是一个数据结构而已
struct Hchan
{
uintgo qcount; // 队列q中的总数据数量
uintgo dataqsiz; // 环形队列q的数据大小
uint16 elemsize;
bool closed;
uint8 elemalign;
Alg* elemalg; // interface for element type
uintgo sendx; // 发送index
uintgo recvx; // 接收index
WaitQ recvq; // 因recv而阻塞的等待队列
WaitQ sendq; // 因send而阻塞的等待队列
Lock;
};
- 环形队列存放channel数据;
- qcount 队列的容量;
- elemsize 队列当前使用量;
- dataqsize 队列的大小;
- elemalg 元素操作的一个Alg结构体;
channel写操作
基本的channel写操作对应runtime.chansend函数
运行时库执行
void runtime·chansend(ChanType *t, Hchan *c, byte *ep, bool *pres, void *pc)
select-case中的chan操作
被编译为if else
crypt/rand包
import “crypto/rand”
rand包实现了用于加解密的更安全的随机数生成器。
math/rand属于伪随机
下面使用crypto/rand生成随机数
这里是rand.go文件的代码内容:
package randDemo
import (
"crypto/rand"
"math/big"
)
// Reader是一个全局、共享的密码用强随机数生成器。在Unix类型系统中,会从/dev/urandom读取;而Windows中会调用CryptGenRandom API。
// rand包实现了用于加解密的更安全的随机数生成器。
func RandInt() (n *big.Int, err error) {
return rand.Int(rand.Reader,big.NewInt(100))
}
这里是rand_test.go文件的代码内容:
package randDemo
import (
"fmt"
"testing"
)
func TestRandInt(t *testing.T) {
RandBigInt,err:=RandInt()
if err!=nil{
t.Error(err)
}
fmt.Println(RandBigInt.Int64())
}
控制台输出结果
GOROOT=/usr/local/Cellar/go/1.13.4/libexec #gosetup
GOPATH=/Users/chenming/go #gosetup
/usr/local/Cellar/go/1.13.4/libexec/bin/go test -c -o /private/var/folders/2n/3tv5n70x4052lhxy8sxm5fpw0000gn/T/___TestRandInt_in_mingTest_randDemo mingTest/randDemo #gosetup
/usr/local/Cellar/go/1.13.4/libexec/bin/go tool test2json -t /private/var/folders/2n/3tv5n70x4052lhxy8sxm5fpw0000gn/T/___TestRandInt_in_mingTest_randDemo -test.v -test.run ^TestRandInt$ #gosetup
=== RUN TestRandInt
45
--- PASS: TestRandInt (0.00s)
PASS
Process finished with exit code 0
fmt包学习
func Print(a ...interface{
}) (n int, err error)
func Println(a ...interface{
}) (n int, err error)
func Printf(format string, a ...interface{
}) (n int, err error)
Print参数列表转换为字符串,标准输出
Println参数列表转换为字符串,最后添加换行符,标准输出
Printf参数列表填写到格式化字符串format占位符中,标准输出
功能同上面三个函数,只不过将转换结果写入到 w 中。
func Fprint(w io.Writer, a ...interface{
}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{
}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{
}) (n int, err error)
功能同上面三个函数,只不过将转换结果以字符串形式返回
func Sprint(a ...interface{
}) string
func Sprintln(a ...interface{
}) string
func Sprintf(format string, a ...interface{
}) string
功能同 Sprintf,只不过结果字符串被包装成了 error 类型。
func Errorf(format string, a ...interface{
}) error
gc垃圾回收
垃圾回收与stop the world
如果垃圾回收想要运行了,那么它会通知其他的goroutine合作停下来,会造成很长的等待时间。
抢占式调度可以在直接剥夺goroutine的运行权。
操作系统调度;
runtime会在后台有一个检测线程,它会检测这些情况,并通知goroutine执行调度。
sysmon函数周期性地做epoll操作,同时检查每个P是否运行了很长时间;Psyscall状态,时间周期20us;
reflect反射
将 reflect.Value反射对象 逆向转换为 某种类型的数据对象
var x float64 = 3.4
v := reflect.ValueOf(x)
o := v.Interface().(float64) // reflect.Value to interface
fmt.Println(o)
** 反射对象是指的 reflect.Value **
通过反射对象
// 正确的获取类型数据的 reflect.Value 的方法
var x float64 = 3.4
v := reflect.ValueOf(&x)
// 获取当前类型的值类型(*V)
p := v.Elem()
// 首先判断 reflect.Value 类型对象是否可以设置新值
ok := p.CanSet()
if ok{
p.SetFloat(5.4)
fmt.Println(x)
}
反射的原理
反射的意思:在运行时,能够动态地知道给定数据对象的类型和结构,并有机会修改它。
如何判断一个数据对象的结构,数据 interface 中保存有结构数据;拿到数据对应的内存地址,将数据转换为 interface,通过查看 interface 的类型结构,就可以知道数据的结构了。
error
错误是什么?
错误是一个接口类型
type error interface {
Error() string
}
只要是实现了 error 接口的类型,都是错误类型。
只要有错误返回都要去检查错误,不要使用_去忽略错误_
不要对错误类型进行断言,对错误的行为进行断言;
对错误进行传递时,每次传递都要进行一次包装,增加描述信息便于定位信息。
栈包装
import "github.com/pkg/errors"
// 调用栈包装
err = errors.WithStack(err)
// 格式化输出栈信息
fmt.Println("%+v",err)
主要用于日志打印上,通过增加_%+v_的格式符输出调出栈信息;
官方包装
// 备注包装
err = fmt.Errorf("B: %w",err)
// 哨兵检测
if err = io.ErrUnexpectedEOF {
...}
// 错误类型转换
var pe os.PathError
if errors.As(err, &pe) {
...pe.Path...}
函数作为值传递
把函数作为一种变量,用type去定义它
那么这个函数就可以作为值传递,
甚至可以实现方法。
作为值传递的条件是类型具有相同的参数以及相同的返回值
type action func(current int) (result int)
包导入过程
Go调用Python
python本身就是个C库,使用cgo可以直接调用,指定正确的编译条件;
可以使用grpc与python进行通讯;
Go的plugin
.so文件的生成
首先创建一个mingTest包,包中创建remainder.go文件
文件内容如下:
package main
import "fmt"
func GetRemainder() {
fmt.Println(150%500)
}
remainder.go文件作为生成动态连接库的源文件;
cd进入remainder.go所在项目的文件夹📁下,使用如下命令生成文件:
go build -buildmode=plugin -o remainder.so remainder.go
注意:这里使用plugin构建模式后,package包名,由remainder包名变成了c
.so文件的使用
加载插件
创建dial.go文件,内容如下:(请参考注释内容)
package remainder
import (
"fmt"
"log"
"plugin"
)
func Dial() {
// 加载插件
p, err := plugin.Open("./remainder.so")
if err != nil {
log.Fatal(err)
}
// 调用插件
GetRemainder, err := p.Lookup("GetRemainder")
if err != nil {
log.Fatal(err)
}
// 使用动态连接库的函数
fmt.Println(GetRemainder.(func(int, int) int)(150, 500))
}
创建测试文件dial_text.go文件
p