go二次学习笔记

go下main函数不支持传入参数

在程序中直接通过os.Args获取命令行参数。

声明全局变量时不能使用 :=

用:=赋值时 如果左边存在全局变量,将会在创建一个同名的局部变量

类型转化:

1. go语言不允许隐式类型转换(强类型语言)
2. 别名和原有类型也不能进行隐式类型转换

go中类型不同不能强制转换
string转成int:
int, err := strconv.Atoi(string)
string转成int64:
int64, err := strconv.ParseInt(string, 10, 64)
int转成string:
string := strconv.Itoa(int)
int64转成string:
string := strconv.FormatInt(int64,10)

类型预定义值

1. math.MaxInt64
2. math.MaxFloat64
3. math.MinUint32

指针类型

1.不支持指针运算
2. string是值类型,其默认的初始化值为空字符串,而不是nil

算术运算符

go语言中没有前置的++或--
用==比较数组时,与其他语言比较引用相比,go 是比较数组的值:数组有相同维度且含有相同个数元素才可以比较,每个元素都相同才相等。

逻辑运算符

&^ 按位置零
右边的二进制位为1,则结果为0;右边的二进制位为0,结果就是左边的二进制位上的数
eg:
1 &^ 0    1
1 &^ 1    0
0 &^ 1    0
0 &^ 0    0

条件和循环

if

1. if中,condition表达式结果必须为布尔值。
2. if支持变量赋值:

if i := 2 > 3;i {

        ...

switch

1. switch条件表达式不限制为整数或者常量
2. 单个case中,可以出现多个结果选项,使用逗号分隔
3. golang中不需要用break来退出一个case
4. 可以不设定switch 之后的条件表达式,在这种情况下,整个switch结构和if...else逻辑作用等同。

数组和切片

数组语法:

var arr[len]int
arr := [...]int{1,2,3}  || arr := [3]int{}
数组遍历:
for i,v := range arr{//i表示索引
    t.Log(v)
}

二维数组中列不能省略大小
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。(go语言是值传递)

数组截取(切片)

切片的底层是数组
切片是共享存储结构,可以有多个切片指向一块内存(数组),当这个数组改变时,所有的切片都会发生改变
语法:

var slice[]int //未初始化切片,值为nil
var slice  = []int{}//已初始化,对未赋值的切片不可以用slice[n]操作。
slice := []int{1,2,3}
slice := arr[:]
slice := make([]T,size,cap) []T: 切片类型   size:切片元素个数  cap:切片容量(就是切片开始位置到数组最后的位置)

make初始化slice后,会给size个元素赋零值,这时候使用append追加,会追加到size之后,使用[:]则会覆盖原来的零值;make初始化map后,不会自动赋零值。


切片是一个结构体,包含三个成员:指针(指向数组)、元素个数(len)、指针指向的数组的容量。

二维切片的定义:

1.
row, column := 3, 4
var answer [][]int
for i := 0; i < row; i++ {
    inline := make([]int, column)
    answer = append(answer, inline)
}

2.
answer1 := make([][]int, row)
for i := range answer1 {
    answer1[i] = make([]int, column)
}

3.
a1 := [][]int{{1, 2, 3}, {4, 5, 6}}
a2 := []int{7, 8, 9}
a1 = append(a1, a2)

判断切片是否为空:len(slice) == 0,nil代表切片为指向数组


append()函数将元素追加到切片的最后并返回该切片,所以要用切片去接受返回来的切片。

当元素个数超过切片容量时,切片会自动进行扩容,每次扩容后都是扩容前的2倍。(扩容时分配新的内存空间,将原来的数据拷贝到新的空间。)
append()函数还支持一次性追加多个元素。


数组与切片的区别

1.容量是否可伸缩
2. 是否可以比较(切片之间不能比较,只能和nil比较)


map

语法:
//var map map[string]int  //不可以这么写
map1 := map[string]int{"age" : 1,"name" : 2}
map2 := map[string]int{}
map2["name"] = 1
map3 := make(map[string]int,10) // 10表示容量,

当访问一个不存在的键时,会返回0,这样会产生歧义,所以,访问map时需要先判断是否存在这个键:
if v,ok := map[3]; ok{
    t.Log("key is  ",v)
}else{
    t.Log("key is not existing")
}

map的遍历
for k,v := range map{ //k表示键
    ...
}

map与工厂模式

map的value可以是一个方法,可以实现一个简单的工厂模式
实现set
go语言内没有内置set,可以使用map[type]bool来实现
func TestMtoSet(t *testing.T) {
    myset := map[int]bool{}
    myset[1] = true
    m := 1
    if myset[m] {
        t.Logf("%d is existing\n", m)
    } else {
        t.Log("not existing")
    }
    t.Log(len(myset)) //统计set中的元素个数
    delete(myset, 1)  //删除set中的元素
}


字符串

与其他编程语言的差异
1. string是数据类型,不是引用或指针类型,因此它的零值是空字符串而不是nil
2. string是只读的(不可变)byte slice,len函数统计的是它所包含的byte数,不是一定是字符数
3. string的byte数组可以存放任何数据
代码
func TestString(t *testing.T) {
    s := "对影成三人"
    t.Log(s)
    s1 := "asd"
    t.Log(s1, len(s1))
    t.Log(len(s))
    //s1[1] = '7'   字符串是一个只读的byte 切片,不可修改
    a1 := [10]byte{'a', 'b', 'c'}
    a1[0] = 's'
    t.Log(string(a1[0])) //byte数组默认输出数字(字符对应的ascii码值)

}

字符串的修改

要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。

转换写法(与类型转换相同):bytes1 := [ ]byte(s)   runes2 := [ ]rune(s)

Unicode UTF8

1. Unicode 是一种字符集

2. UTF8 是unicode的存储实现(转换为字节序列的规则)

rune数组:Go 使用rune 类型来处理 Unicode

一个汉字在byte数组里占3-4位,在rune数组里占一位。

func TestStringToRune(t *testing.T) {
    s := "两处有情方可断"
    s1 := "两"
    c := []rune(s1)
    t.Log(s, len(s))
    t.Log(c, len(c))
    t.Logf("两 unicode %c", c[0])
    //byte的遍历  按照一个字节遍历
    for i := 0; i < len(s); i++ {
        t.Logf("%v", s[i])
    }

    //rune的遍历  按照一个rune字符遍历
    for _, c := range s {
        t.Logf("%c", c)
    }
}


常用字符串函数

func TestStringFn(t *testing.T) {
    s := "A,B,C"
    parts := strings.Split(s, ",") //分割
    for _, part := range parts {
        t.Log(part)
    }
    t.Log(strings.Join(parts, "-")) //增加连接符

}

函数

  • 函数可以有多个返回值
  • 所有的参数都是值传递:slice,map,channel有传引用的错觉(修改函数内的slice,slice副本,其本身的值也会改变)。
  • 函数可以作为变量的值
  • 函数可以作为参数和返回值

代码:
func TimeSpent(inner func(op int) int) func(op int) int {//函数做参数、返回值
    return func(n int) int {
        start := time.Now()
        ret := inner(n)
        fmt.Println("time spent:", time.Since(start).Seconds())
        return ret
    }
}

func slowfun(op int) int {
    time.Sleep(time.Second * 2)
    return op
}

func TestFn(t *testing.T) {
    fhhs := TimeSpent(slowfun) //返回的是一个函数赋值给fhhs这个变量
    second := fhhs(100)
    t.Log(second)
}


defer函数

go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。(栈)
recover/panic
panic可以在任何地方引发,但recover只有在defer调用的函数中有效。
recover()必须搭配defer使用。
defer一定要在引发panic的语句前定义

常见的“错误恢复”:

defer func(){
    if err := recover(); err != nil{
        log.Error("ercovered panic",err)
    }
}()
上面的代码只是将panic错误记录在日志里,很多涉及到重要资源的错误都会被recover恢复,最后造成损失。

defer机制:

1.defer执行顺序:先进后出
2. defer被声明时,其参数会被实时解析

func f44() (r int) {
    r = 2
    defer func() {
        r = r + 5
    }()
    return r
}//返回值为7

func f44() (r int) {
    r = 2
    defer func(r int) {    //这里的r与返回值r不是同一个
        r = r + 5 //这里的r与返回值r不是同一个
    }()
    return r
}//返回值为2

defer后跟函数时,会立即对函数里的变量进行计算求值

结构体创建及初始化

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
e := ygw{"1234", "ygw", 12}
e1 := ygw{id: "12345", age: 12}
e2 := new(ygw) //返回指针   等价于  e2 := &ygw{"1", "www", 19}
e2.id = "dsadas"
e2.name = "ygw"
e2.age = 19
通过实例的指针访问成员不需要使用 ->
空结构体是不占用空间的。

方法和接收者:

方法是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者。

接收者类型可以是指针类型和非指针类型。

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。

当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

什么时候应该使用指针类型接收者?

需要修改接收者中的值
接收者是拷贝代价比较大的大对象
保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。


编译器会对调用者进行取地址和指针解引用操作,这就使得值类型调用者可以调用指针类型接收者方法,指针类型调用者也可以调用值类型接收者方法,

T和*T是两种不同的类型元数据,他们有各自的方法列表,但是他们不能有同名的方法;因为编译器为了支持接口,会生成值类型接收者方法同名的指针类型接收者方法;也就是说T的方法列表是*T方法列表的子集。


接口

接口为非侵入性,实现不依赖于接口定义,所以接口的定义可以包含在接口使用者包内。

指针类接口可以实现指针接收者类方法和值接收者类方法(编译器会自动生成值接收者类型方法同名的指针类型接收者方法)。

值类接口只可以实现值接收者类方法。

空接口及断言
func duanyan(p interface{}){
    if i,ok := p.(int);ok{
        fmt.Println("is int")
    }else{
        ...
    }
}


接口的嵌套

较大的接口可以由多个小接口定义组合而成,但使用者最好只依赖必要功能的最小接口。
type interf3 interface{
    interf1
    interf2
}


扩展和复用

go本身不支持继承但可以通过结构体嵌套实现一个类似继承的效果实际上并不是继承,不符合LSP原则,且方法不能重载

普通嵌套

type Animal struct {
    name string
}
type Dog struct {
    a *Animal //通过嵌套匿名结构体实现继承
}
func (a *Animal) move() {
    fmt.Printf("会动")
}
d1 := &Dog{
    Feet: 4,
    a : &Animal{ //注意嵌套的是结构体指针
        name: "乐乐",
    },
}
d1.a.move() //会动


匿名嵌套

type Animal struct {
    name string
}
type Dog struct {
    *Animal //通过嵌套匿名结构体实现继承
}
func (a *Animal) move() {
    fmt.Printf("会动")
}
d1 := &Dog{
    Feet: 4,
    Animal: &Animal{ //注意嵌套的是结构体指针
        name: "乐乐",
    },
}
d1.move() //会动


多态:

两个实例实现一个接口,通过接口对象调用这个接口内的方法时,会使用其各自的方法。


panic 和 os.Exit

1. os.Exit 退出时不会调用defer指定的函数。
2. os.Exit 退出时不会输出当前调用栈信息。
3. panic用于不可恢复的错误。(recover可以恢复)
4. panic退出前会执行defer指定的内容

Go的依赖管理

1.package是基本复用模块单元,以首字母大写表明可被包外代码访问。
2. 代码的package可以和所在的目录不一致
3. 同一目录里的Go代码的package要保持一致。

4. 同一个包的函数(package相同),不需要import导入包就可以直接使用,函数名等不需要首字母大写也可以调用。

GOROOT:go的安装路径(无需设置)

GOPATH:存放依赖包的目录(最初的管理方法)

GOPROXY:代理网站地址

go的依赖发展

Golang的依赖管理,经历了三个阶段,最初就是使用GOPATH,官方建议只有一个GOPATH,把项目都放到GOPATH下,而且GOPATH下需要src,bin,pkg三个文件夹,src用来放源码,bin目录用来放编译好好的可执行文件,pkg用来放编译过后引用的第三方包。

如果你用go get命令下载第三方包,那么会下载到src目录下,引用的时候,golang解释器回去GOROOT和GOPATH的src下找。

这样做的问题时,如果项目A和项目B都在GOPATH下,同时都引用了第三方包C,但一个引用C1.0版本,一个引用了C2.0版本,按照这种情况,ABC都在src目录下,如何区分C的版本呢?

为了解决这个问题,Golang的依赖管理出现了第二个阶段,使用vendor目录。go v1.5以后会在各自的项目下创建一个vendor目录,将第三方依赖放到vendor目录下,解释器也会优先去找vendor目录下的第三方包,这样就可以解决问题。常用的一些第三方依赖包,也就是用来管理vendor的。这个关系就很像pip之于python,maven之于java。但这仍然无法解决很难用的GOPATH。

第三个阶段,使用go module。Go1.11版本之后开始使用的go module已经完全不依赖于GOPATH了,可以说完美解决了解决第三方依赖的问题,启用了Go Module模式之后,不一定非要将代码写到GOPATH目录下,所以也就不需要我们再自己配置GOPATH了,使用默认的即可。当初始化命令执行完毕之后,会在go_module目录下生成一个go.mod文件,该文件就是用来引入GoPath目录下的第三方依赖的文件。当我们需要引入GoPath目录下的第三方依赖包的时候,只需要在go.mod目录下添加依赖名称,GoModule就会自动帮我们把第三方依赖包下载到GoPath目录下。

GoPath我们用来存放我们从网上拉取的第三方依赖包。
GoModule我们用来存放我们自己的Golang项目文件,当我们自己的项目需要依赖第三方的包的时候,我们通过GoModule目录下的一个go.mod文件来引用GoPath目录src包下的第三方依赖即可。

导入自己的包时,应该把我们要导入的包放到GOPATH路径下的src文件夹中

gopath只需要设置在src的上级目录

包名是从$GOPATH/src/后开始计算的

调用函数时使用包名

导入包时,如果识别不了,可以使用import 别名导入

图示:

import(
    "mainygw/test729"//一个项目下导入 : mainygw 是go.mod里的依赖  test729是要导入的包的文件夹名
	"package729" //通过gopath导入:package是gopath:/src目录下要导入的包的文件夹名称
)

一个项目内:

gopath:


init方法

在main被执行前,所有依赖的package的init方法都会被执行。
不同包的init函数按照包导入的依赖关系决定执行顺序
每个包可以有多个init函数
包的每个源文件也可以有多个init函数。

协程

Thread vs Goroutine 
1. 创建时默认的stack大小
Thread 1M   Goroutine 2K,协程更为轻量级。
2. 和内核对象(KSE)的对应关系
Java Thread 1 : 1 当线程切换时,内核态也需要切换,这是很大的开销
Goroutine m :n


Goroutine对运行时间长的协程的处理机制

会定期检查处理完的协程数,如果长时间未变化,就将当前协程排到队列尾部。


当某个协程被系统io中断了(它的执行情况会保存在寄存器里,之后继续运行),cpu会去处理其他协程,当io结束后,这个协程会排在某一个协程队列或是全局等待队列中。

变量在协程中共享,被竞争使用,如果使用外部的变量i,会产生资源竞争。

func TestGoroutine(t *testing.T) {
    for i := 0; i < 10; i++ {
        go func(i int) {
            println(i)
        }(i) //值传递,外部的i改变不影响这里的i
    }
}

互斥锁

func TestCon(t *testing.T) {
    var mut sync.Mutex
    count := 0
    for i := 0; i < 2000; i++ {
        go func() {
            defer func() {
    mut.Unlock()
            }()
            mut.Lock()
            count++
        }()
    }
    time.Sleep(time.Second * 3)//sleep要在输出前
    t.Log(count)

}

WaitGroup

func TestG(t *testing.T) {
    var wg sync.WaitGroup
    var mt sync.Mutex
    count := 0
    for i := 0; i < 2000; i++ {
        wg.Add(1)
        go func() {
            defer func() {
                wg.Done()
    mt.Unlock()
            }()
            mt.Lock() //仍然需要加锁,避免竞争
            count++
        }()
    }
    wg.Wait()
    t.Log(count)
}

读写互斥锁

互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。

读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。

package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	x      int64
	wg     sync.WaitGroup
	lock   sync.Mutex
	rwlock sync.RWMutex
)

func write() {
	//rwlock.Lock()
	lock.Lock()
	x = x + 1
	time.Sleep(10 * time.Millisecond)
	lock.Unlock()
	wg.Done()
}

func read() {
	//rwlock.RLock()
	lock.Lock()
	time.Sleep(time.Millisecond)
	lock.Unlock()
	wg.Done()
}

func main() {
	start := time.Now()
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go write()
	}
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go read()
	}
	wg.Wait()
	end := time.Now()
	fmt.Println(end.Sub(start))//互斥锁需要15s,而读写互斥锁只需要137ms
}


 

CSP和Actor

Actor:直接通讯
CSP:通过Channel进行通讯(通讯的双方耦合性更低),通过通信共享内存而不是通过共享内存而实现通信。
go中的channel有容量限制且独立于处理Groutine,Actor中的mailbox容量无限,接收进程也是被动地处理消息。

channel:

通道是引用类型,通道类型的空值是nil。

语法:

make(chan 类型,缓冲大小)

channel操作

通道有发送(send)、接收(receive)和关闭(close)三种操作。
发送:将一个值发送到通道中。 ch <-10
接收:从一个通道中接收值。 <-ch 
关闭: close(ch)

无缓冲的通道只有在有人接收值的时候才能发送值。

就像你住的小区没有快递柜和代收点,快递员给你打电话必须要把这个物品送到你的手中,简单来说就是无缓冲的通道必须有接收才能发送。
单单执行ch <- 10这一行代码形成死锁。


只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道,关闭后向所有接收者广播发送完毕的信息,这样就不需要关注接收者的数量,很大程度上降低了耦合度。

通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。

关闭后的通道有以下特点:

  1. 对一个关闭的通道再发送值就会导致panic。
  2. 对一个关闭的通道进行接收会一直获取值直到通道为空。
  3. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
  4. 关闭一个已经关闭的通道会导致panic。

for range 从通道循环取值

for i := range ch2 { // 通道关闭后会退出for range循环
    fmt.Println(i)
}


单向通道

很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。

func counter(ch1 chan<- string){

}
func printer (ch2  <-chan string){
     for i := range ch2{
         fmt.Println(i)
     }
}

多路选择和超时

select

  • 可处理一个或多个channel的发送/接收操作。
  • 如果多个case同时满足,select会随机选择一个。
  • 对于没有case的select{}会一直等待,可用于阻塞main函数。
  • case无顺序

select {
    case ret := <-ch:
        t.Log(ret)
    case <-time.After(time.Second * 2): //超时
        t.Error("time out")
    default:
        t.Error("no one returned")
}

任务的取消

package cancel

import (
	"fmt"
	"testing"
	"time"
)

func iscanceled(cchan chan struct{}) bool {
	select {
	case <-cchan:
		return true
	default:
		return false
	}
}

func cancel(cchan chan struct{}) {
	close(cchan) //close后,向所有channel广播
}
func cancel2(cchan chan struct{}) {
	cchan <- struct{}{} //向cchan发送一个值,使select捕捉到
}

func TestCancel(t *testing.T) {
	cchan := make(chan struct{}, 0)
	for i := 0; i < 5; i++ {
		go func(i int, cchan chan struct{}) {
			for {
				if iscanceled(cchan) {
					break
				}
				time.Sleep(time.Second * 5)
			}
			fmt.Println(i, "DONE")
		}(i, cchan)
	}
	cancel(cchan)
	time.Sleep(time.Second * 1)
}

Context与任务取消

为了更加彻底的取消任务(取消任务相关的子任务)使用Context来完成

package context_test

import (
	"context"
	"fmt"
	"testing"
	"time"
)

func iscanceled(ctx context.Context) bool {
	select {
	case <-ctx.Done():
		return true
	default:
		return false
	}
}

func TestCancel(t *testing.T) {
	//函数返回的cancel是一个取消函数,ctxBackground函数获取父节点
	ctx, cancel := context.WithCancel(context.Background())
	for i := 0; i < 5; i++ {
		go func(i int, ctx context.Context) {
			for {
				if iscanceled(ctx) {
					break
				}
				time.Sleep(time.Second * 5)
			}
			fmt.Println(i, "canceled")
		}(i, ctx)
	}
	cancel()
	time.Sleep(time.Second * 1)
}

仅执行一次(单例模式)

package once_test

import (
	"fmt"
	"sync"
	"testing"
	"unsafe"
)

type Singleton struct {
}

var slt *Singleton
var once sync.Once

func getsingletonobj() *Singleton {
	once.Do(func() { //仅被执行一次
		fmt.Println("Create obj")
		slt = new(Singleton)
	})
	return slt
}

func TestGetsingletonobj(t *testing.T) {
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			obj := getsingletonobj()
			fmt.Printf("%x\n", unsafe.Pointer(obj))
			wg.Done()
		}()
	}
	wg.Wait()
}

仅需完成任意任务

package response_test

import (
    . "fmt"
    "testing"
    "time"
)

func run_task(i int) string {
    time.Sleep(1 * time.Second)
    return Sprintf("yin cha xian la %d", i)
}

func response_one() string {
    ch1 := make(chan string)
    for i := 0; i < 5; i++ {
        go func(i int) {
            ch1 <- run_task(i)
        }(i)
    }
    return <-ch1 //return后有很多routine阻塞在发送消息,造成很多僵尸进程,讲chan设为buffer chan以改进。

}

func TestResponse(t *testing.T) {
    t.Log(response_one())
}

使用bufferchannel实现对象池

package pool

import (
	"errors"
	//. "fmt"
	"time"
)

type robj struct {
}
type objpool struct {
	bufchan chan *robj
}

func NewObjPool(num int) *objpool {
	objpool := objpool{}
	objpool.bufchan = make(chan *robj, num)
	for i := 0; i < num; i++ {
		objpool.bufchan <- &robj{}
	}
	return &objpool
}

func (p *objpool) GetObj(timeout time.Duration) (*robj, error) {
	select {
	case ret := <-p.bufchan:
		return ret, nil
	case <-time.After(timeout):
		return nil, errors.New("time out")

	}
}

func (p *objpool) ReleaseObj(obj *robj) error {
	select {
	case p.bufchan <- obj:
		return nil
	default:
		return errors.New("overflow")
	}
}


func main() {
	pool := NewObjPool(12)
	for i := 0; i < 11; i++ {
		if v, err := pool.GetObj(time.Second * 2); err != nil {
			errors.New("error")
		} else {
			Printf("%T\n", v)
			if err := pool.ReleaseObj(v); err != nil {
				errors.New("error")
			}
		}
	}
	Println("na ni")
}


sync.Pool 对象缓存池

sync.Pool对象的获取机制:

首先会尝试从私有对象获取,如果私有对象不存在,就尝试从当前Processor的共享池获取;如果当前Processor共享池也是空的,就尝试从其他Processor的共享池获取。
如果所有池子都是空的,那么用户指定New函数产生一个新的对象返回。
私有对象协程安全,共享池协程不安全。(私有对象并非池,只有一个对象)

sync.Pool的放回机制:

优先往私有对象里放,如果私有对象存在,放入当前Processor子池的共享池中。

sync.Pool 对象的生命周期:
GC会清除sync.pool缓存的对象,对象的缓存有效期为下一次GC之前

sync.Pool总结

sync.Pool适用于通过复用,降低复杂对象的创建和GC代价.

协程安全,会有锁的开销

但它的生命周期由GC(系统调动)决定,因此不适合做连接池等需要自己管理生命周期资源的池化。


benchmark对代码性能的测评


BDD

BDD不是关于测试的,是在应用程序存在之前,写出用例与期望,从而描述应用程序的行为,并且促使在项目中的人们彼此互相沟通。

反射

反射是指在程序运行期间对程序本身进行访问和修改的能力。程序在编译的时候,变量转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

应用场景:

序列化和反序列化
数据库获取数据
获取配置项数据等等

类型断言vs反射
类型断言只能一个类型一个类型地猜
反射可以直接获取数据类型

TypeOf

obj := reflect.TypeOf(x)
fmt.Println(obj)//返回的是一个数据类型
fmt.Printf("%T\n",obj)//返回的是一个reflect.type指针

type name和type kind

在反射中关于类型还划分为两种:类型(Type)种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到.kind()。 

func reflectType(x interface{}) {
	t := reflect.TypeOf(x)
	fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}

Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。

ValueOf

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。

obj := reflect.ValueOf(x)
fmt.Println(obj)//返回的是一个值
fmt.Printf("%T\n",obj)//返回的是reflect.value

 通过反射获取值

func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)
	k := v.Kind()
	switch k {
	case reflect.Int64:
		// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
		fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
	case reflect.Float32:
		// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
		fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
	case reflect.Float64:
		// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
		fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
	}
}

 通过反射设置变量的值

想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。

Elem()在反射中根据指针取对应的值:

func reflectSetValue2(x interface{}) {
	v := reflect.ValueOf(x)
	// 反射中使用 Elem()方法获取指针对应的值
	if v.Elem().Kind() == reflect.Int64 {
		v.Elem().SetInt(200)
	}
}

isNil()和isValid()

isNil():isNil(v) 报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。

isValid():isValid(v):返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。

isNil()常被用于判断指针是否为空;isValid()常被用于判定返回值是否有效。

结构体反射

实现代码 

type student struct{
	Name string `json:"name"`
	Age int `json:"age" `
}
func (s student) Study(i int) {
	m1 := " i good good study"
	fmt.Println(m1)
}

func (s student) Sleep(i int){
	m2 := " i good good sleep"
	fmt.Println(m2)
} 

func reflectstruct(a interface{}){
	t :=reflect.TypeOf(a)
	v := reflect.ValueOf(a)

	fmt.Printf("name:%v kind:%v\n",t.Name(),t.Kind())
	//通过索引取结构体中字段
	for i := 0; i < t.NumField(); i++{
		field := t.Field(i)
		fmt.Printf("name is %v type is %v json tag is %v\n",field.Name,field.Type,field.Tag.Get("json"))
		
	} 
	//根据名字取结构体中字段
	if nameField,ok := t.FieldByName("Age");ok{
		fmt.Printf("name %s,index %d type %v json tag %v\n",nameField.Name,nameField.Index,nameField.Type,nameField.Tag.Get("json"))
	}

	v1 := t.NumMethod()
	v2 := v.NumMethod()
	fmt.Println(v1,v2)
	for i := 0; i < v1; i++{
		fmt.Printf("method name is %s\n",t.Method(i).Name)
		fmt.Printf("method type is %s\n",v.Method(i).Type())

		// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
		args := []reflect.Value{}
		v.Method(i).Call(args[0])
	}



func TestReflect(t *testing.T){
	stu := student{
	    Name :"亚托克斯",
	    Age : 10000,
	}
	reflectstruct(stu)
}

不安全编程
unsafe.Pointer转换
b := *(* [ ]int)(unsafe.Pointer(&a))

 

性能优化

锁的优化

sync.Map

 sync.Map:
通过减少锁的影响范围,减少锁发生冲突的概率来提高性能。
一部分只读,一部分可读可写,访问数据时,首先会查找只读的那一部分,如果查找不到,就去另一部分去找。多次访问的可读可写区的数据会添加到只读部分。两部分都是指针指向数据。

  Concurrent Map

 Concurrent Map:
将整个Map划分为多个区域,然后加锁,减小发生锁冲突的概率。适用与读写都很频繁的情况

  

 GC友好

 避免内存分配和复制

  1. 复杂对象尽量传递引用:数组传递、结构体传递
  2. 初始化至合适的大小:自动扩容是有代价的
  3. 复用内存

工具:

高效字符串函数

HTTP服务

路由规则:

这里指的都是写在后端函数里的url

软件架构

面向错误的设计

面向恢复的设计

  • 健康检查:注意僵尸进程 :池化资源耗尽以及死锁等阻塞情况
  • 减少recover():无法解决的错误不要recover()恢复,let it crash!
  • 构建可恢复的系统:

1.拒绝单体系统

2.面向错误和恢复的设计:

在依赖服务不可用时,可以继续存活(通过服务降级来保证服务存活)。

快速启动(依赖服务重启恢复)。

无状态(当发生错误时,所有状态未来得及迁移,就会损失掉)。

  • 向客户端协商

一些常用的包

Context包

在go并发编程中,多个goroutine会一同处理一个任务,context用来通知goroutine取消任务,传递键值对信息。

context包含一个接口Context,四个具体实现emptyCtx、cancelCtx、timerCtx、valueCtx,六个函数;

emptyCtx没有实际意义,通过Background和TODO函数来创建;

cancelCtx是一种可取消的Context,包含done(用于获取该context的取消通知)、children(存储以当前节点为根节点的所有节点)、err(用来存储取消时的错误信息)、mu(保证线程安全);WithCancel函数返回cancelCtx和一个用于取消context的取消函数。

timerCtx在cancelCtx的基础上封装了定时器和截止时间,这样既可以主动取消又可以在到达deadline时,自动取消;通过WithDeadline和WithTimeout函数返回。WithDeadline指定一个时间点,WithTimeout指定一个时间段。

valueCtx支持键值对打包,可以给Context附加一个键值对信息;通过withValue函数返回。

context.Background():上下文的默认值(创建上下文的默认起点),这个函数返回一个空context,返回值就是根节点,这只能用于最高等级(在main函数、初始化或顶级请求处理中),因为所有其他的上下文都应该从它衍生(Derived)出来

context.TODO():通常用作占位符或临时解决方案,只在不确定应该使用哪种上下文时使用

说明:

1)、background 和 todo 本质上都是 emptyCtx 结构体类型(即是一个不可取消,没有设置截止时间,没有携带任何值的Context)

2)、给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO

泛型

Go 1.18版本增加了对泛型的支持;泛型可以支持多种参数类型,提高代码适用性。

与interface{}的区别:以interface{} 为参数类型的函数,需要对参数进行类型断言,转换成对应的类型,本质上并不是适配多种参数类型。

语法:

func MapKeys[Key  string | int | float64 , Val any](m map[Key]Val) []Key 

func MapKeys[Key comparable, Val any](m map[Key]Val) []Key 

comparable,any为参数类型的约束

默认的约束类型: any 全部类型  comparable 所有可以比较的类型,比如int、uint、float、struct(结构体可以比较?)、指针、类型大小相同的数组 等

tip:

不同类型的 struct 之间不能进行比较,编译期就会报错(GoLand 会直接提示)

同类型的 struct 也分为两种情况:

struct 中所有成员都是可以比较的,则该 strcut 的不同实例可以比较

struct 中含有不可比较的成员(如 Slice),则该 struct 不可以比较

泛型类型

//定义泛型slice

type Slice [T int| float32| float64]  [ ]T  //配合泛型,自定义类型Slice 可以是多种类型的切片

var a Slice[int] = []int{}  //实例化时需要指定类型

var b Slice[float32] = []float32{}

a = b //这里会报错,虽然都是Slice类型,但是不可以把b赋值给a 

//定义泛型map

type Mymap[key int | string | float32, Value int | string] map[key]Value

var m1 Mymap[int,string] = map[int]string{}

//定义泛型struct

type Mystruct [T int|string] struct{

        Id T

        Name string

}

泛型函数和泛型方法

type Mymap[key int | string | float32, Value int | string] map[key]Value

func add[T int | string](a, b T) T { //泛型函数
	return a + b
}

func (m Mymap[key, Value]) Madd() { //泛型方法
	fmt.Println(m[key(1)])
}

func main() {
    fmt.Println(add[string]("aa", "as"))  //打印:aaas
    //参数是可识别类型(非自定义类型),则可以不用写泛型类型:fmt.Println(add("aa", "as")) 
    var mm Mymap[int, string] = map[int]string{
	    1: "luopole",
	    2: "jiarenmen",
    }
    mm.Madd() //打印:luopole
}

自定义泛型约束

type myint int

type Myint interface{

        ~int | int8 | int32                //这里的~ 作用是支持int所有的衍生类型,比如myint

}

func GetMax [T Myint](a,b T) T { //Myint作为泛型约束

        if a > b{

                return a

        }

        return b         

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值