Go基础、标准库使用

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

  1. go使用分段栈,初始分配很小,可动态增长。在进入函数时,插入检测指令实现。
  2. goroutine是协作式的,运行到调用runtime库时就有机会进行调度。
  3. 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,这种函数我们一般叫闭包。闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问。

导包顺序

golang导包顺序

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)

包导入过程

golang包导入过程

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值