golang

基础

1. 目录

├─bin			# 执行文件
├─pkg			# 外部引入package
└─src			# go代码
    └─project01	# 项目目录
        ├─coupon# coupon包
        └─main	# main包,入口文件

2. GOROOT

go bin的路径

3. GOPATH

代码的路径,现在又module管理,都不需要设置了

4. go mod init path

project01是项目的根目录,此时需要调用go mod init project01让project01成为模块根,当调用其他package时就可以直接用project01/coupon绝对目录来引用。不需要将代码放置gopath的目录下了

5. 内置函数

  • append

6. 代码可见性

  • 大写字母开头的就是对其他包(package)可见
  • 小写字母开头就是所在包(package)可见,其他package不可见

7. init函数

  • init函数不能被其他函数调用,并且在main函数之前自动被调用
  • 每个包可以拥有多个init函数
  • 每个包中的源文件也可以拥有多个init函数
  • 同一个包中多个init函数的执行顺序没有明确定义
  • 不同包中的init函数按照导入的依赖关系决定init函数的执行顺序

8. main函数

  • main函数只能属于main包
  • 一个main包只能有一个main函数

9. 运算符

9.1. 算数运算符

+-*/%

9.2. 关系运算符

==!=>>=<<=

9.3. 逻辑运算符

&&||

9.4. 位运算符

运算符描述
&二进制 与
|二进制 或
^二进制 或与,两个不同时位1
<<n<<m n二进制向左移m位,相当于n * 2^m。二进制低位补0
>>n>>m n二进制向右移m位,相遇当n/2^m

10. 下划线

10.1. 下划线在import中

当下划线在import中时,只引入该包的init()方法,其他的方法不引入

代码结构

    └─project01	# 项目目录
        ├─hello# hello包
        	└─test.go
        └─main	# main包,入口文件
        	└─main.go

main.go

package main 

import _ "project01/hello"

func main() {
    hello.Hello()	
    //编译错误,.\main.go:6:2: undefined: hello
}

test.go

package hello

import (
	"fmt"
)

func init() {
    fmt.PrintLn(123)
}

func Hello() {
    fmt.PrintLn(456)
}

10.2. 下划线在代码中

下划线相当于占位符,把不想要的值赋值给下划线

package main

import (
    "os"
)

func main() {
    buf := make([]byte, 1024)
    f, _ := os.Open("/Users/***/Desktop/text.txt")
}

11. 变量

  • 同一作用于不支持重复声明
  • 声明后必须使用
  • 变量在声明时会根据类型定义自动初始化

11.1. 标准声明

// 单个变量声明
var param type 

// 多变量声明
var (
	param1 type
    param2 type
)

11.2. 类型推导

var name = "is string"
var num = 1

11.3. 短变量声明

在函数内部可以使用更简洁的:=方式声明并初始化变量

package main 
import "fmt"

var num int		//全局变量
func main() {
    num := 123	//局部变量
    fmt.Println(num)
}

12. 常量

  • 声明时必须初始化
  • 声明后不可更改
//单常量声明
const pi = 3.1415

//多常量声明
const (
	pi = 3.14
    e = 2.7182
)

//多常量,如果省略值,和前一行赋值一样
const (
	pi1 = 3.14
    pi2
    pi3
)

13. 基本类型

13.1. 整型

  • 长度:int8,int16,int32,int64
  • 无符号:uint8,uint16,uint32,uint64

13.2. 浮点型

  • float32,float64

13.3. 复数

  • y = ax+b a是虚部 b是实部

  • complex64:实部和虚部为32位

  • complex32:实部和虚部为64位

13.4. 布尔

  • true or false

13.5. 字符串

  • 单行字符串:双引号包"含着的
  • 多行字符串:用反引号`包含着
13.5.1. 字符串常用操作
  • len(str) :求长度
  • str + str or fmt.Sprintf():拼接字符串
  • strings.Split():分割
  • strings.Contains():是否包含
  • strings.HasPrefix():前缀判断
  • strings.hasSuffix():后缀判断
  • strings.Index() or strings.LastIndex():子串出现的位置
  • strings.Join()

13.6. byte和rune类型

组成字符串的单个元素叫做字符。用单引号包含着

//byte,asc2码
var b := 'x'	

//rune类型,utf-8字符,一个rune字符由一个或多个byte组成
var a := '发'

13.7. 基本类型的强制类型转换

string(param)
int(param)

# stringint
strconv.Atoi(int_param)
strconv.Itoa(str_param)

14. 数组array

golang 数组和以为数组有很大不同:

  • 是一种固定长度并且同一个数组中数据类型一致的序列
  • 访问越界会报错
  • 数组是值类型,赋值和传参会赋值整个数组,而不是指针。当改变副本时,原数组不会改变

14.1. 初始化

14.1.1. 一维数组
//通过数字确定数组长度,长度5
var arr0 [5]int = [5]int{1,2,3,4,5}	
var arr1 = [5]int{1,2,3,4,5}
//通过初始化确定长度,长度6
var arr2 = [...]int{1,2,3,4,5,6}
//初始化局部
var arr3 = [5]string{3: "this is 3", 5: "this is 5"}
//结构数组
var arr4 = [...]struct{
    name string
    sex int8
}{
    {"qiu", 1},
    {"luo", 0},
}
14.1.2. 多维数组
var arr1 = [...][3]int{
    {1,2,3}, 
    {2,3,4}
}

15. slice

  • zslice切片是数组的一个引用,切片不储存任何数据,它只是描述了底层数组中的一段,修改切片会修改其底层数组中对应的元素
  • 切片是引用类型,零值是nil,即没有分配内存地址。
  • 切片长度可以改变,是一个可变长度的数组
  • 切片的切割遵守 左闭右开( s[0:8] 就是切割index 0 -7的元素)
  • 切片拥有长度( len(s) )和容量 ( cap(s) )
    • 长度是当前切片内的元素
    • 容量是切片的开端到底层数组的末端长度,例如 数组有10个元素,切片等于s[4:8],此时长度是4=(8-4),容量是6=(10-4)
  • 切片再切片(设计到底层,具体看示例)

15.1. 初始化

// 不需要定义长度的是slice,需要长度的是数组

//全局
var a []int

//局部
b := []int{}

//初始len长度的slice,最大扩容是cap
c := make([]int, len_int, cap_int)

15.2. 操作

arr := [...]int{1,2,3,4,5,6,7,8,9}
slice1 := arr[:]
value := slice1[n]
len(slice1)
cap(slice1)
  • s[n]:获取切片中key为n的值

  • s[:]:从切片/数组位置0到len(s-1)所获得的切片

  • s[start:end]:从切片/数组位置start到end所获得的切片

  • len():获取切片/数组长度

  • cap():获取切片最大扩容数

15.3. 内存分布

image.png

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

	s1 := a1[2:4]
	s2 := a1[1:4]

	//两个内存地址是相同的,说明两个slice.pointer指向同一个数组
	fmt.Println(&s1[0], &s2[1])	// 0xc000018240 0xc000018240

image.png

15.4. 切片再切片

从上图可以看出切分的结构体是

type slice struct {
    *Pointer
    Len int
    Cap int
}
  • *Pointer是指向底层指针的数组
  • Len是切片内元素个数
  • Cap是切分的容量
package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)	//len=6 cap=6 [2 3 5 7 11 13]

	// 截取切片使其长度为 0
	s1 := s[:0]
	printSlice(s1)	//len=0 cap=6 []

	// 拓展其长度
	s2 := s1[1:3]
	printSlice(s2)	//len=2 cap=5 [3 5]

	// 舍弃前两个值
	s = s[2:]	
	printSlice(s)	//len=4 cap=4 [5 7 11 13]
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

上述输出解释:

  • s[:0],切了0:0所以元素个数是0个,可是从切片开端数组末端就是底层数组的长度,所以cap是6
  • s1[1:3],是其实就是根据s1的Pointer找到底层数组,切底层数组的1:3,所以返回了[3,5],cap从切片开端2开始(3-1 左闭右开)到底层数组末端6,所以返回4

15.5. append

a := [...]string{1,2,3,4,5,6,7,8,9}
s := a[:]
s = append(s, 10)

当往一个满容量的切分中追加元素,会发生扩容,内存地址会发生改变,底层会拷贝出一个一样的数组,Pointer指向新数组地址

15.6. string 转 byte切片数组

str := "abcdef"
arr := [...]byte(str)

16. 指针

  • &取地址符,获取变量的内存地址
  • *根据内存地址取值
  • 每种指针 根据内存地址内的值的类型 不同还划分了指针类型*int *int64 *string
func main() {
    //取指针
    a := 10
    b := &a
    fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
    fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
    fmt.Println(&b)                    // 0xc00000e018
    
    //根据内存地址取值
    c := *b
    fmt.Println(c)					   // 10
    
    //给空指针赋值
    var d *int
    d = new(int)	//一般很少用new
    *d = 10
    fmt.Println(*d)						//10
}	

image.png

16.1. 空指针

  • 当一个指针被定义后,没有分配到任何变量时,他的指为nil
  • 指针的定义var ptr *string
  • 空指针的判断if ptr == nil

16.2. 指针的初始化和赋值

16.2.1. new
  • new内置函数的签名
func new(Type) *Type

new()函数返回的是一个指针

  • Type:类型,定义了指针指向类型
  • *Type:new函数的返回指针

var p *Type只是声明了一个指针变量并没有初始化,指针变量需要初始化后才会分配内存空间,之后才能进行赋值操操。new()函数及分配内存空间

func main() {
	a := 3
	var ptr *int
	ptr = new(int)
	ptr = &a
	fmt.Println(*ptr)		//print 3
}
16.2.2. make

make也是分配内存的作用,区别于new(),它只用于slicemapchannel的内存创建,而且它返回的类型就是这3个类型的本身,而不是他们的指针,因为这3种类型本来就是引用类型,所以不需要返回他们的指针。

map内置函数的签名

func make(t Type, size, ...IntegerType) type
func main() {
	s1 := []string{"a", "b", "c", "d", "e", "f"}
	s2 := make([]string, 6)
	s2 = s1
	fmt.Printf("%p\n", &s1)	//0xc000004078
	fmt.Printf("%p\n", s1)	//0xc0000220c0
	fmt.Printf("%p\n", s2)	//0xc0000220c0
}

引用类型:内存存放的是 数据的内存地址。所以修改数据时,两个指针指向同一个地址时他们的值都会被修改!!!!!!!!!!!!!!

17. map

map是一种无序的key-value数据结构,map是引用类型,必须初始化后才能赋值。

  • map定义
map[keyType]valueType

keyType:键类型
valueType: 值类型
  • map初始化
make(map[keyType]ValueType [, cap]) 	//cap是map的容量大小,不设置也会默认给定容量大小

var map1 = make(map[string]int, 8)		// 可以不指定长度,可是slice必须指定长度
  • 声明时填充元素
func main() {
    map1 := map[string]int{
        "key1": "value1",
        "key2": "value2",
    }
}
  • map的无序遍历:go并不会按定义时的kv顺序进行遍历
	map1 := make(map[string]string)
	map1["name"] = "alei"
	map1["age"] = "18"

	for k,v := range map1 {
		fmt.Println("k:"+k)
		fmt.Println("v:"+v)
	}
  • key是否存在 value, boolval := map[]
func main() {
	map1 := make(map[string]string)
	map1["name"] = "alei"
	map1["age"] = "18"

	_, exist := map1["key"]		// '_'占位符,不需要的值
	if exist {
		fmt.Println("yes")
	} else{
		fmt.Println("no")		//print no
	}
}
  • 删除键值对
func main() {
	scoreMap := make(map[string]int)
	scoreMap["张三"] = 90
	scoreMap["小明"] = 100
	scoreMap["王五"] = 60
	delete(scoreMap, "小明")//将小明:100从map中删除
    fmt.Println(scoreMap["小明"])		//print 0
    
    _, isExist := scoreMap["小明"]
	if isExist {
		fmt.Println("yes")
	} else {
		fmt.Println("no")			//print no
	}
}

18. 结构体

18.1. type关键字

18.1.1. 创建新类型

是一个新的类型,并且可以创建自己类型的方法

type MyString string

func (ms MyString) call () {
    fmt.Println("is mystring")
}

func main(){
	var a MyString = "sas"
	a.call()					// is mystring
    fmt.Printf("type is %T", a)	// type is main.MyString
}

18.1.2. 类型别名

只是换了一种类型的名字,无法创建自己的方法

type MyString = string

func main(){
	var a MyString = "sas"
	fmt.Printf("type is %T", a)	// type is string

}
18.1.3. 结构体

18.2. 结构体

结构体是一种自定义数据类型,可以封装多个基础数据类型。go没有类和继承,他通过结构体来实现面向对象

  • struct定义
type comsuter struct {
	fields_name Type
	fields_name Type
}

//实例
type student struct {
    name string
    age int
    sex int
}

18.3. 实例化

  • 结构体基本实例化,他也是一种引用类型,所有需要先分配内存才能赋值
type student struct {
    name string
    age int
    sex int
}

  • 构造体实例化

返回的是结构体内容,并不是地址

var student1 student 
student1.name = "qiu"
student1.age = 18
fmt.Println(student1)	//print {qiu 18}
  • 匿名结构体基础实例化
var student1 struct{name string, age int, sex int}   
  • new()创建内存指针,实现实例化。注意new()返回的是结构体的地址
type student struct {
    name string
    age int
    sex int
}

student1 := new(student)
student1.name = "测试"		//student1.name = "博客"其实在底层是(*student1).name = "博客",这是Go语言帮我们实现的语法糖。
student1.age = 18
fmt.Println(student1)		//print   &{qiu 18 0}  这里是一指针,可是返回的不是内存地址的值
  • 取结构体的地址实例化

&struct相当于new()实例化操作,注意同new也是返回结构体的地址

type student struct {
    name string
    age int
    sex int
}

student1 := &student
student1.name = "测试"		//student1.name = "博客"其实在底层是(*student1).name = "博客",这是Go语言帮我们实现的语法糖。
student1.age = 18
fmt.Println(student1)		//print   &{qiu 18 0}  这里是一指针,可是返回的不是内存地址的值

18.4. 初始化

初始化已经把实例化合为一步。

  • 使用键值对 对结构体进行实例化
type student struct {
	name string
	age int
	sex int
}

func main() {
	student1 := student{
		name: "qiu",
		age : 18,
	}
	fmt.Println(student1)	//print  {qiu 18 0}
}
  • 使用键值对 对结构体指针进行实例化,效果同上
type student struct {
	name string
	age int
	sex int
}

func main() {
	student1 := &student{
		name: "qiu",
		age : 18,
	}
	fmt.Println(student1)	//print   &{qiu 18 0}  
}
  • 省略键值 ,必须要所有字段都初始化 并且 按顺序
type student struct {
	name string
	age int
	sex int
}

func main() {
	student1 := &student{
		"qiu",
		18,
        1,
	}
	fmt.Println(student1)	//print   &{qiu 18 0} 
}
  • 结构体定义和初始化同时定义
package main

import "fmt"

func main() {
	struct1 := struct {
		a int
		b string
	}{
		a : 10,
		b : "string",
	}
	fmt.Printf("struct1 = %v", struct1)
}

结构体的内存是连续的内存地址

18.5. 构造函数

Go语言没有类的概念,所以没有构造函数。不过可以自己去实现一个构造函数

type student struct {
	name string
	age int
	sex int
}

func newStudent(name string, age int, sex int) student{
	return student{
		name,
		age,
		sex,
	}
}

func main() {
	stu := newStudent("qiu", 17, 1)
	fmt.Println(stu.name)
}

18.6. 方法和接收者

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

18.6.1. 定义格式
func (接收者 接收者类型) 方法名(参数列表) 返回参数 {
    
}
18.6.2. 指针类型的接收者 实例
  • 指针类型接收者,可以在任意位置更改唯一属性内容
package main

import "fmt"

type student struct {
	name string
	age int
}

func (stu *student) setAge (age int) int {
	stu.age = age
	return age
}

func main () {
	a := student{
		"qiushenglei",
		18,
	}

	fmt.Println(a.age)		//print 18
	a.setAge(19)
	fmt.Println(a.age)		//print 19 	指针引用,相当于php的对象引用
}
18.6.3. 值类型的接收者 实例
package main

import "fmt"

type student struct {
	name string
	age int
}

func (stu student) setAge (age int) int {
	stu.age = age
	return age
}

func main () {
	a := student{
		"qiushenglei",
		18,
	}

	fmt.Println(a.age)		// print 18
	a.setAge(19)
	fmt.Println(a.age)		// print 18
}
18.6.4. 结构体的嵌套

嵌套结构体的子结构不要使用匿名字段(有重名的情况),否则程序产生歧义,无法编译

//Address 地址结构体
type Address struct {
    Province string
    City     string
}

//User 用户结构体
type User struct {
    Name    string
    Gender  string
    Address Address
}

func main() {
    user1 := User{
        Name:   "pprof",
        Gender: "女",
        Address: Address{
            Province: "黑龙江",
            City:     "哈尔滨",
        },
    }
    fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}
18.6.5. 结构体的继承

通过两个结构体嵌套

package main

import "fmt"

//Animal 动物
type Animal struct {
	name string
}

func (a *Animal) move() {
	fmt.Printf("%s会动!\n", a.name)
}

//Dog 狗
type Dog struct {
	Feet    int8
	Animal //通过嵌套匿名结构体实现继承
}

func (d *Dog) wang() {
	fmt.Printf("%s会汪汪汪~\n", d.name)
}

func main() {
	d1 := &Dog{
		Feet: 4,     
		Animal: Animal{
			name: "乐乐",
		},
	}
	d1.wang() //乐乐会汪汪汪~
	d1.move() //乐乐会动!
}
18.6.6. 结构体字段的可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

变量结构

  • 一个变量有两个属性
    • type, 变量的类型,类型又划分为两种
      • 静态类型,例如int, string, slice
      • 动态类型,interface
  • value, 变量的值

断言

  • 当一个参数是动态类型时,对某种类型做特殊处理时,可以使用断言
  • 使用方法 var.(Type)
	var a interface{}	// 必须时interface,否则后面a.(int)会编译失败
	a = 3
	if value,ok := a.(int); ok {
		fmt.Println("a is int, value is ", value)
	} else {
		fmt.Println("a is not int")
	}

反射 reflect

流程控制

19. if条件判断

19.1. 使用1

if语句的init 定义的变量,作用域只在if / else 结构体

if init; condition {
    
}

//案例
if err := recover() ; err != nil {
    fmt.Printf("error = %v", err)
}

19.2. 使用2

if condition [&& | || condition] {

} else if condition {
    
} else {
    
}

20. switch

switch从上执行到下,直到匹配某个case为止。可以省略break,默认自动终止。

var和case里面的数据类型必须保持一致

switch var {
    case val1:
    case val2:
    case val3, val4, val5:
    default:
    
}

// 省略var参数,相当于switch true,此时case应该设置成判断条件
switch {
    case a<10:
    	fmt.Println("A less than 10")
	case a<20:
    	fmt.Println("A less than 20")
    default:
   		fmt.Println("A more than 20")
}

21. select

阻塞监听 信号channel

  • 如果使用for语句嵌套,就会轮询监听
  • 如果没有for语句嵌套,监听到一个case就跳出select
func reqTask(ctx context.Context, name string) {
	for {
		select {
        // 其实这里还是监听信号,  **多个子协程**不用争抢,都会监听到信号,不像channel多协程时,只有一个协程获得
		case <-ctx.Done():
			fmt.Println("stop", name)
			return
		default:
			fmt.Println(name, "send request")
			time.Sleep(1 * time.Second)
		}
	}
}

22. 循环for

共有3中for的使用方式

  • 普通for
//init; condition; post
for a:=0; a<10; a++ {
    
}


  • 替代while
// condition 
for a<10 {		//替代了while

}
  • 死循环
for {
    break;
}

23. 循环语句range

range类似于迭代器,返回 【索引,值】 或 【键,值】

range可以对 slice、map、array、string、channel等进行迭代循环。

for k,v := range var1 {
}

函数

24. 函数定义

  • 参数需要定义类型param Type
  • 多个连续一样类型的参数,除了最后一个不能省略类型,其他的都可以省略a, b int
  • 如果有返回值,需要定义返回值类型列表
  • 可以有0个或多个返回值
package main

import "fmt"

func main() {
	map1 := map[string]string{
		"a": "a",
		"b": "b",
	}

	str1 := "nihao"
	r1, r2 := funcName(1, 2, str1, map1)
	fmt.Println(r1, r2)
}

func funcName(a, b int, c string, d map[string]string) (map[string]string, string) {
	return d,c
}

25. 函数参数

25.1. 参数传递类型

  • 值传递,默认是值传递,当调用方法时,实际是复制了一份参数到函数内,函数内对数据进行修改不会影响外部变量
  • 引用传递,当调用方式时,是复制了一份引用类型参数的内存地址并传递到函数中,函数内对数据进行修改会影响到外部变量
package main

import "fmt"

func main() {
	map1 := map[string]string{
		"a": "a",
		"b": "b",
	}

	str1 := "nihao"

	noRet(&str1, map1)
	fmt.Println(map1)		// print  map[a:cccc b:b]
}

func noRet(c *string, d map[string]string) {
	d["a"] = "cccc"		
	fmt.Println(*c)			// print nihao
}

25.2. 不定参数传参

不定参数传值就是函数的参数个数和类型是不确定的。它的本质上就是一个slice参数接受了传参,只能定义再函数的最后一个参数

package main

import "fmt"

func main() {
	map1 := map[string]string{
		"a": "a",
		"b": "b",
	}

	str1 := "nihao"
	arr1 := [...]string{"a", "b", "c", "d"}
	//r1, r2 := funcName(1, 2, str1, map1)
	//fmt.Println(r1, r2)

	sendSlice(&str1, map1, 1, 2, 4)

	sendInterface(&str1, map1, 1, 2, arr1)
}


func sendSlice(c *string, d map[string]string, e ...int) {
	fmt.Printf("%T", e)		//print  []int[1 2 4]   返回的类型是slice
	fmt.Println(e)			//print	[1, 2, 4]
}

func sendInterface(c *string, d map[string]string, e ...interface{}) {
	fmt.Printf("%T", e)		//print	[]interface {}	返回的类型是interface
	fmt.Println(e)			//print	[1 2 [a b c d]]
}

26. 函数返回值

  • 如果有返回值,函数体必须定义返回值类型。
  • 能有多个返回值
  • 在接受返回值时,可以使用_占位符忽略某个值
  • 没有参数的return语句返回各个返回变量的当前值
package main

import "fmt"

func main() {
	map1 := map[string]string{
		"a": "a",
		"b": "b",
	}

	str1 := "nihao"
	r1, r2 := funcName1(1, 2, str1, map1)
	_, r3 := funcName2(1, 2, str1, map1)		// '_'占位符忽略了e返回值
    fmt.Printf("r1 = %v\n", r1)					// print r1 = map[a:a b:b]
	fmt.Println("r2 = " + r2)					// print r2 = nihao
	fmt.Println("r3 = " + r3)					// print r3 = this is f
  	
}

//隐式返回值
func funcName1(a, b int, c string, d map[string]string) (map[string]string, string) {
	return d,c
}

//显示返回值,在函数定义时,定义了参数名
func funcName2(a, b int, c string, d map[string]string) (e map[string]string, f string) {
	e = d
	f = "this is f"
	return
}

26.1. 函数返回值步骤

如果方法返回值有变量申明

  1. 在方法第一行就会创建变量
  2. return前,会将return的值赋值给变量
func test () (a string, b int) {
    // 虚拟 a:=""  b:=0
    x:="aaa"
    y:=4
    defer func() {
		fmt.Printf("x:%v y:%v", x, y)	//x="aaa", y=4
	}()
    return x+bbb, b-1		//a="aaabbb", b=3, 
}

func test1 () (x string, y int) {
    x="aaa"
    y=4
    defer func() {
		fmt.Printf("x:%v y:%v", x, y)	//x="aaabbb", y=3  这里还能看来,先执行return前的赋值操作,然后再执行defer
	}()
    return x+bbb, b-1		
}

如果方法返回值没有变量申明

func test () (int, string) {
	x := 1
	y := "aaa"
	defer func() {
        fmt.Printf("x:%v y:%v", x, y)	//x=1 y=aaa
	}()

	return x+1 ,y+"bbb"
}

27. 匿名函数

package main

import "fmt"

func main() {
	//匿名函数
	fn1 := func (a int) int {return a+1}
	fmt.Printf("fn1 type is %T, Value is %v\n", fn1, fn1(13))

	//匿名函数集合collection
	fn2 := [](func (a int) int){
		func (a int) int {return a + 1},
		func (a int) int {return a + 2},
	}
	fmt.Printf("fn2 type is %T, Value is %v\n", fn2, fn2[1](14))

	//匿名函数结构体,结构体的定义和初始化
	fn3 := struct {
		p1 func(int) int
		p2 func(string) string
	}{
		p1: func (a int) int {
			return a+1
		},
		p2: func (a string) string {
			return "hello,"+a
		},
	}
	fmt.Printf("fn3 type is %T, Value is %v\n", fn3, fn3.p2("Qiu"))
}

28. 闭包

函数内返回了一个匿名函数,通过外部函数的赋值参数调用内部函数,它的堆栈不会被GC回收,保存再堆栈中

package main

import "fmt"

// 返回2个函数类型的返回值
func test01(base int) (func(int) int, func(int) int) {
	// 定义2个函数,并返回
	// 相加
	add := func(i int) int {
		base += i
		return base
	}
	// 相减
	sub := func(i int) int {
		base -= i
		return base
	}
	// 返回
	return add, sub
}

func main() {
	f1, f2 := test01(10)
	// base一直是没有消
	fmt.Println(f1(1), f2(2))		//print  11   9
	// 此时base是9
	fmt.Println(f1(3), f2(4))		//print	 12   8
    
    f3, f4 := test01(10)
	// base一直是没有消
	fmt.Println(f3(1), f4(2))		//print  11   9
	// 此时base是9
	fmt.Println(f3(3), f4(4))		//print	 12   8
}

通过上述结果表明,闭包函数会从外部函数的堆栈获取数据内容,所以base是会f1和f2是共用的

29. 延时调用defer

29.1. defer特性

  1. defer用于注册延迟调用
  2. return之前才会被调用(如果return是个表达式,那么先执行表达式后再执行defer)
  3. 多个defer语句,用到栈的特性,先进后出(FILO)
  4. defer语句中的变量,在defer声明时就决定了

29.2. defer用途

  1. 关闭文件句柄
  2. 资源释放

29.3. 实例

  • defer语句中的变量,在defer声明时就已经决定的案例 和 FILO
package main

import "fmt"

func main() {
	arr := []string{"a","b","c","d","e","f","g","h"}

	for k,v := range arr {
		defer fmt.Println(k, v)
	}

	fmt.Println("before return")
}

//print result
before return
7 h
6 g
5 f
4 e
3 d
2 c
1 b
0 a
  • defer匿名函数导致的结果 实例
package main

import "fmt"

func main() {
	arr := []string{"a","b","c","d","e","f","g","h"}

	for k,v := range arr {
		defer func() {fmt.Println(k, v)}()
	}

	fmt.Println("before return")
}

//print result
before return
7 h
7 h
7 h
7 h
7 h
7 h
7 h
7 h

会发现 为什么全是 7h,因为defer匿名函数的参数并不是通过参数被传入的,被执行时代码已经把业务逻辑跑完了此时k赋值在了7 v赋值成了h,所以结果成了7h.

总结:每次执行“defer”语句时,调用的函数值和参数都会像往常一样被计算并重新保存,但实际的函数不会被调用。

  • 待完

30. 异常处理

panic()抛出异常,然后在defer中通过recover()捕获异常

30.1. panic

  • panic()是内置函数
  • 假如函数F中调用了panic(),会立即终止其后的代码,触发函数内defer
  • 返回函数的调用者G,在G中,调用函数F语句之后的代码也不会执行,发出调用者的defer
  • 直至协程退出,并报告错误
  • 通过painc 传递 异常error
func panic (error Interface{}) {}

30.2. recover

  • recover()是内置函数
  • 用来控制一个协程的异常行为,捕获异常,从而影响应用的行为
  • 在defer 延迟函数中,通过recover()来终止一个协程的panicking过程,从而恢复正常代码的执行【在recover()在defer中说明:panic()执行之前,会触发 defer 执行】
  • 捕获painc传递的异常错误error

注意!!!

recover()处理panic(error)的error,defer只能定义在panic()之前,并且recover()定义在defer 调用的函数之中

recover()处理异常后,代码执行不会走到F函数的panic之后,而是走到G调用者的F函数语句之后

func recover() Interface{} {}

30.3. 实例

30.3.1. 基本使用实例
package main

import "fmt"

type excption struct {
	err_code int
	msg      string
}

func main() {
	test()
}

func test() {
	a := 1
	b := 2
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("error = %v", err)
		}
	}()

	defer func() {
		fmt.Println(a)
	}()

	err := excption{
		err_code: 1,
		msg:      "gan,报错了",
	}
	panic(err)
	fmt.Println(b)
}

//print
1						  //说明了先进后出
error = {1 gan,报错了}		//抛出了定义的panic

30.3.2. 多panic实例

recover() 只捕获了最后一个 panic

package main

import "fmt"

func test() {
    defer func() {
        fmt.Println(recover())
    }()

    defer func() {
        panic("defer panic")
    }()

    panic("test panic")
}

func main() {
    test()
}

//print
defer panic		//说明:recover() 只捕获了最后一个 panic
30.3.3. 无效recover实例

只有在defer调用的函数内直接调用recover(),捕获才有效

package main

import "fmt"

func test() {
    defer func() {
        fmt.Println(recover()) //有效
    }()
    defer recover()              //无效!
    defer fmt.Println(recover()) //无效!
    defer func() {
        func() {
            println("defer inner")
            recover() //无效!
        }()
    }()

    panic("test panic")
}

func main() {
    test()
}

//print
defer inner
<nil>
test panic

方法

31. 方法定义

方法就是结构体struct实例的函数。

go方法总是绑定对象实例(接收者receiver),并隐式将实例作为第一参数(receiver)

  • 只能在当前包内命名 类型定义方法 (类型:结构体或者类型别名)
  • 第一参数 receiver可任意命名,如果方法中未使用,可以省略参数名
  • receiver 参数类型可以 T 或者是 *T,T类型不能是接口或指针【T是值传递相当于copy了一份数据传递,*T是直接内存指针传递,*T是会直接修改实例对象的】
  • 不支持重载
type Test struct{
    a int
    b string
}

func (receiver receiverType) funcName (paramList) returnType {}

// T 的值传递
func (t Test) method1 (param []int, param1 map[string]int) map[string]int {
    
}

// T 的 指针传递
func (t *Test) method1 (param []int, param1 map[string]int) map[string]int {
    t.a = 123 //外部实例会被修改
}

32. 匿名字段

  • 结构体内只定义引用的结构体的类型,不标记结构体变量

  • 可以像 字段成员 那样调用 匿名字段的方法

  • 当有多个匿名字段时,他们不能有相同的子方法否则编译报错,因为编译器不知道调用哪个方法。显式的则可以使用

package main

import "fmt"

type User struct {
    id   int
    name string
}

type Manager struct {
    User				//Manager包含匿名字段User,只定义了一个结构体类型
}

func (self *User) ToString() string { // receiver = &(Manager.User)
    return fmt.Sprintf("User: %p, %v", self, self)
}

func main() {
    m := Manager{User{1, "Tom"}}
    fmt.Printf("Manager: %p\n", &m)
    fmt.Println(m.ToString())		//继承了User的ToString方法
}

33. 结构体嵌套的方法集

  • 类型 T 的方法集 包含 receiver T 的方法
  • 类型 *T 的方法集 包含 receiver T + receiver *T 方法
  • 类型 S 包含了 匿名字段T,S 和 *S 方法集包含了 receiver T方法
    • S = receiver S + receiver T
    • *S = receiver S + receiver *S + receiver T
  • 类型 S 包含了 匿名字段*T,S 和 *S 方法集包含了 receiver T + receiver *T方法
    • S = receiver S + receiver T + receiver *T
    • S = receiver S + receiver *S+ receiver T + receiver *T
package main

import "fmt"

type User struct {
	id   int
	name string
}

func (self *User) Test() {
	self.id = 2
}

func main() {
	u := User{1, "Tom"}
	u.Test()
	fmt.Printf("%v, %v\n", u.id, u.name)
}

//print

2, Tom	//u调用 receiver *User 被自动转成了&u

u调用 receiver *User 时,会被自动转成 &u,所以方法集有变更,所以上面的结论是否有问题。有待考证

34. 表达式

34.1. method value

instance.method(args)
  • receiver被复制,后面的修改不是同一个内存地址
package main

import "fmt"

type User struct {
    id   int
    name string
}

func (self User) Test() {
    fmt.Println(self)
}

func main() {
    u := User{1, "Tom"}
    mValue := u.Test // 立即复制 receiver,因为不是指针类型,不受后续修改影响。

    u.id, u.name = 2, "Jack"
    u.Test()

    mValue()
}

//print 
{2 Jack}		
{1 Tom}			//receiver被复制,所以后续的修改不受影响

并发编程

35. 并发、并行

  • 并发:一个CPU时间片,多个进/线程交替执行
  • 并行:一个时间片个,多个进程在执行(多核CPU才能做到)

36. 协程

  • 进程:进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位,对的PCB (Process Control Block)
  • 线程:线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,一个进程中可以有多个线程,它们公用PCB,可是他们又有自己的独立TCB(Thread Control Block)
  • 协程:用户态的线程

协程结构体

type g struct {
    goid int64 //协程id
    status uint32	// 协程状态
    stack struct {
        lo uintptr	//该协程拥有的栈低位
        hi uintptr	//该协程拥有的栈低位
    }
    sched gobuf	//切换时保存的上下文信息
    startfunc uintptr // 程序地址
}

type gobuf struct {
    sp uintptr	//栈指针位置
    pc uintptr 	//运行到的程序位置
}

36.1. 特点

  • 用户态自行调度:进/线程切换都是操作系统调度的IO,而go的协程是自己GPM的**runtime(运行时)**层实现

  • 可增长的栈:OS线程是固定栈2kb,go协程因为是自己的调度,所以会按需增减

  • **主协程并不会主动等待子协程完成,而会直接退出,退出时子协程也会退出销毁。**但是可以调用wait()来阻塞主协程来等待子协程

36.2. Channel

go协程通信:用通信来实现内存共享

channel像数据类型的queue,有着FIFO(先进先出)的特性,保证收发数据的顺序

36.2.1. 类型

channel是引用类型,所以需要定义并初始化(赋予内存),空值是nil

// 定义channel通道类型

var c0 chan int			//int类型的chan
var c1 chan string		//string类型的chan
var c2 chan []string 	//切片类型的chan
36.2.2. 创建channel

make(chan 元素类型, [缓冲大小])

c0 := make(chan int)
c0 := make(chan string)
c0 := make(chan []int])
36.2.3. 操作

发送和接受都使用<-符号

send
var c0 chan int
c0 := make(chan int)
ch <- 10
receive
c1 := make(chan string)
a := <- c1		//从c1取出,并赋值给a
<- c1			//从c1取出,并忽略值
close

channel是会被GC(垃圾回收)回收,不像文件,文件在使用完成后需close(fd),而channel不是必须的,当接收方或者发送发一者关闭通道,则通道关闭无法使用

close(c1)

特征:

  • 对一个关闭的channel发送值 panic
  • 对一个关闭的channel接收值,会一直读取成功,直到管道内数据为空
  • 对一个关闭的并且没有值的管道执行接收操作,会得到对应类型的空值
  • 关闭一个已关闭的通道会导致panic
36.2.4. 无缓冲通道

c0 := make(chan int)这样定义并初始化的通道叫做无缓冲通道。

​ 如果当在协程g0c0投递数据时,没有其他的协程在接收c0管道的数据时,就会产生panic(deadlock死锁),相当于小区内没有快递箱和代售点,导致送货小哥阻塞。如果需要破解则需要在g0投递消息时,g1协程已经在监听接收消息

死锁的案例

package main

import (
	"fmt"
	"time"
)

func main() {
	channel := make(chan int)
	channel <- 20
	for i:=0; i<10; i++ {
		go getChanData(channel)
	}
	time.Sleep(time.Second)
}

func getChanData(channel chan int) {
	a := <- channel
	fmt.Println(a)

}

//print
fatal error: all goroutines are asleep - deadlock!

无死锁案例,但是其余9个接受数据协程阻塞

package main

import (
   "fmt"
   "time"
)

func main() {
   channel := make(chan int)

   for i:=0; i<10; i++ {
      go getChanData(channel, i)
   }

   channel <- 20
   time.Sleep(5*time.Second)
}

func getChanData(channel chan int, chan_num int) {
   a := <- channel
   fmt.Printf("chan_%v: %v", chan_num, a)
}

//print
chan_0: 20  	//其余9个协程都没有打印数据,说明全部阻塞监听通道,当主协程退出,其他协程则退出并销毁
36.2.5. 有缓冲通道

​ 在初始化通道时,赋予channel的容量,则是一个有缓冲通道,指定了通道的可存入数据长度make(chan int, 10)

​ 可以通过内置函数len(c1)来获取chan当前存入的元素数量,cap(c1)获取通道的容量

module

当go命令查找一个新模块时,它会去检查goproxy坏境变量,它是一个由逗号分隔的列表

  • 一个代理路径:需要通过goproxy protocol进行通信

  • direct:需要一个版本控制系统, git,svn等

  • off:不需要通信,GOPRIVATE 和 GONOPROXY环境变量也是用来控制这个行为

官网:https://go.dev/ref/mod#modules-overview

37. goproxy

在大陆地区我们无法直接通过 go get 命令获取到一些第三方包,

  • 最常见的就是 golang.org/x 下面的各种优秀的包
  • 自己私有gitlab仓库代码的引用

goproxy 是一个开源项目,当用户请求一个依赖库时,如果它发现本地没有这份代码就会自动请求源,然后缓存到本地,用户就可以从 goproxy.io 请求到数据。此时就用到了goproxy protocol

37.1. goproxy protocol

  • 获取源目标版本列表: b a s e / base/ base/module/@v/list,例如http://goproxy.xiaoe-tools.com/talkcheap.xiaoeknow.com/eboss/gin_awesome_pkg/@v/list

  • 获取源目标go.mod: b a s e / base/ base/module/@v/$version.mod

  • 获取源目标信息: b a s e / base/ base/module/@v/$version.info

  • 获取源目标信息source code : b a s e / base/ base/module/@v/$version.zip

37.2. 私有git库免密拉取

文档:https://cloud.tencent.com/developer/article/1422220

athens文档:https://gomods.io/zh/intro/components/

cd ~
vim .netrc

// .netrc文件内容
machine github.com		// 请求地址
  login MY_USERNAME		// 账号
  password MY_PASSWORD	// 密码

垃圾回收GC

38. 常见垃圾GC方法

  • 引用计数reference counting:php的GC,每个对象都有一个被引用的计数器,每被引用一次则被引用对象计数器+1,当引用对象失效,则被引用对象计数器-1,当计数器是0时,可回收
    • 优点:实时性好,当计数器变0,触发GC清理内存
    • 缺点:当AB对象是嵌套引用,则AB对象的计数器不会变成0,导致无法被回收
  • 标记-清除Mark and Sweep:每次启动垃圾回收都会暂停当前所有的正常代码执行,运行一个扫描线程重新运行系统获得链路,通过链路判断对象是否可被触达,如果能触发说明对象当前正在被使用,不可回收;反之,没有触达到的对象则认为无用,可以回收。
  • 三色标记法:是mark and sweep的优化版,go语言用的此方法

39. STW (Stop The World)

STW,stop the world;让程序暂停,GC扫描标记标记GCROOTS的对象引用。这样会让系统有卡顿现象

40. 减少STW时的三色标记法的缺陷弥补

不启动STW的情况下:

  • A:扫描并标记完对象颜色(状态)后,突然添加白色(未引用状态)对象的引用,GC还是会清除白色对象,导致对象丢失
  • B:扫描并标记完对象颜色(状态)后,突然减去黑色(已引用状态)对象的引用,GC还是会清除黑色对象,导致对象未被清除

【GO面试精要】GC内存管理机制

三色标记、混合写屏障GC模式图文全分析

GMP

Golang调度器GMP原理与调度全分析

【GO面试精要】GMP并发模型、Goroutine

包使用

41. 信号接收

监听终端信号,退出死循环

signals := make(chan os.Signal, 1)		// os信号通道
signal.Notify(signals, os.Interrupt)	// 监听 os.Interrupt终端信号 ,写入通道中

consumed := 0
ConsumerLoop:	// 标签代码块必须包着 select、for循环
for {
    select {
        case msg := <-partitionConsumer.Messages():
        log.Printf("ID %d ; Consumed  message offset %d next offset: %d\n", goroutineId, msg.Offset, partitionConsumer.HighWaterMarkOffset())
        log.Printf("ID %d ; Consumed message key: %v  value:%v \n", goroutineId, string(msg.Key), string(msg.Value))
        consumed++

        case <-signals:	// select 通道,如果有数据直接break ConsumerLoop标签
        break ConsumerLoop
    }
}

42. 终端输入

// fmt包
    fmt.Scanln()
    fmt.Println("请输入您的姓名,年龄,薪水,是否通过考试, 使用空格隔开")
    fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
}

// os包
var buffer [512]byte

n, err := os.Stdin.Read(buffer[:])
if err != nil {

    fmt.Println("read error:", err)
    return

}

fmt.Println("count:", n, ", msg:", string(buffer[:]))

43. 读文件

import os

// 按行读取
file, err := os.Open("app-2019-06-01.log")
if err != nil {
    log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
    lineText := scanner.Text()
}

// 读取整个文件
b, err := ioutil.ReadFile("app-2019-06-01.log") // just pass the file name
    if err != nil {
        fmt.Print(err)
    }

str := string(b) // convert content to a 'string'

fmt.Println(str) // print the content as a 'string'

44. 协程锁

44.1. 互斥锁Mutex

package main

import (
	"fmt"
	"os"
	"os/signal"
	"sync"
	"time"
)

var mutex sync.Mutex //互斥锁

func main() {
	go user1()
	go user2()

	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)
	select {
	case <-signalChan:
		fmt.Println("catch interrupt signal")
		break
	}
}


func printer(str string) {
	//fmt.Println("will get lock")
	mutex.Lock()         //加锁
	defer mutex.Unlock() //解锁
	//fmt.Println("get lock ok")
	for _, ch := range str {
		fmt.Printf("%c", ch)
		time.Sleep(time.Millisecond * 300)
	}
}
func user1() {
	printer("hello ")
}
func user2() {
	printer("world")
}

//打印结果
worldhello 或者 helloworld: 两个单词是有序的,说明某个协程会在mutex.Lock()进行自旋等待获取锁

44.2. 读写互斥锁RWMutex

  • 当获取写锁,并未释放时,读锁阻塞等待
mutex := &sync.RWMutex{}
 
mutex.Lock()
// Update 共享变量
mutex.Unlock()
 
mutex.RLock()
// Read 共享变量
mutex.RUnlock()

44.3. sync.WaitGroup

sync.WaitGroup也是一个经常会用到的同步原语,它的使用场景是在一个goroutine等待一组goroutine执行完成。

sync.WaitGroup拥有一个内部计数器。当计数器等于0时,则Wait()方法会立即返回。否则它将阻塞执行Wait()方法的goroutine直到计数器等于0时为止。

要增加计数器,我们必须使用Add(int)方法。要减少它,我们可以使用Done()(将计数器减1),也可以传递负数给Add方法把计数器减少指定大小,Done()方法底层就是通过Add(-1)实现的。

var wg sync.WaitGroup

for i:=0;i<3;i++ {
    wg.Add(i)
    go func(i int) {
        defer wg.Done()
        fmt.Printf("this is %d goroutine", i)
    }(i)
}

wg.Wait()

44.4. sync.Once

确保代码块只被调用一次

once := &sync.Once{}
for i := 0; i < 4; i++ {
    i := i
    go func() {
        once.Do(func() {
            fmt.Printf("first %d\n", i)
        })
    }()
}

golang并发变成之同步原语 – 网管叨bi叨

45. 主协程退出,通知子协程

ctx := context.WithCancel(context.Background())
go func() {
		for {
			select {
			// 主进程退出,通知consumer关闭
			case <-ctx.Done():
				_ = client.Close()
				//logger.Infof("quit: kafka consumer %s", groupId)
				return
			}
		}
	}()

46. 测试用例

46.1. 测试

  • 文件名以_test.go结尾
  • 测试用例方法以Test开头
  • 测试用例的入参只能是*testing.T
  • 命令go test -run=funcname
  • 子测试使用t.Run()
func TestDB(t *testing.T) {
	// 参数结构体
	type args struct {
		AppID string
	}

	// 用例定义
	tests := []struct {
		name string
		args args
		want Config
	}{
		{name: "case1", args: args{AppID: "app029yPjQc9131"}, want: Config{AppID: "app029yPjQc9131"}},
		{name: "case2", args: args{AppID: "app029yPjQc9132"}, want: Config{AppID: "app029yPjQc9131"}},
	}

	for _, tt := range tests {
		// 子测试
		t.Run(tt.name, func(t *testing.T) {
			if got, _ := opeDB(tt.args.AppID); got != tt.want {
				t.Errorf("opeDB() = %v, want %v", got, tt.want)
			}
		})
	}
}

46.2. 基准测试benchmark

  • 用例方法以Benchmark开头
  • 执行命令 go test -bench=funcname -run=none
    • -bench=?, 模糊匹配benchmark防范
    • -run=?,模糊匹配测试单元方法(一般不会有none这种方法名)
  • 基准函数会运行目标代码 b.N 次。在基准执行期间,会调整 b.N 直到基准测试函数持续足够长的时间。
// 串行执行压测 
func BenchmarkHello(b *testing.B) {
	for i := 0; i < b.N; i++ {
		foo()
	}
}

// 输出`BenchmarkHello    10000000    282 ns/op`  循环了10000000,每次循环耗时282纳秒
// goroutine 并发执行
func BenchmarkSprints(b *testing.B) {
    // 起GOMAXPROCS个数的协程
    b.RunParallel(func(pb *testing.PB) {
        // 每个协程 执行b.N次
        for pb.Next() {
            // do something
            fmt.Sprint("代码轶事")
        }
    })
}

46.3. Main

有时测试用例方法内是需要一些全局变量的,所以需要在测试用例执行方法前初始化变量。而testing.M就可以达到

func TestMain(m *testing.M) {
	fmt.Println("start test main")
	// 初始化连接
	initProviders()

	// 触发测试用例
	m.Run()

	// 用例完成后,工作
	teardown()
}

47. context

  • 主协程控制子协程关闭
// 控制关闭
func reqTask(ctx context.Context, name string) {
	for {
		select {
        // 其实这里还是监听信号,  **多个子协程**不用争抢,都会监听到信号,不像channel多协程时,只有一个协程获得
		case <-ctx.Done():
			fmt.Println("stop", name)
			return
		default:
			fmt.Println(name, "send request")
			time.Sleep(1 * time.Second)
		}
	}
}

func main() {
    // 1.生成控制子协程关闭context
	ctx, cancel := context.WithCancel(context.Background())
	go reqTask(ctx, "worker1")
	go reqTask(ctx, "worker2")
	time.Sleep(1 * time.Second)
    // 调用1回调的cancel() 关闭子协程
	cancel()
	time.Sleep(1 * time.Second)
}
  • 参数透传
// 参数透传
type Options struct{ Interval time.Duration }

func reqTask(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("stop", name)
			return
		default:
			fmt.Println(name, "send request")
			op := ctx.Value("options").(*Options)
			time.Sleep(op.Interval * time.Second)
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	vCtx := context.WithValue(ctx, "options", &Options{1})

	go reqTask(vCtx, "worker1")
	go reqTask(vCtx, "worker2")

	time.Sleep(3 * time.Second)
	cancel()
	time.Sleep(3 * time.Second)
}
  • 定时关闭:多长时间段(time duration)关闭
func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	go handle(ctx, 1500*time.Millisecond)
	select {
	case <-ctx.Done():
		fmt.Println("main", ctx.Err())
	}
}

func handle(ctx context.Context, duration time.Duration) {
	select {
	case <-ctx.Done():
		fmt.Println("handle", ctx.Err())
	case <-time.After(duration):
		fmt.Println("process request with", duration)
	}
}
  • 超时关闭:具体时间关闭
func contextDeadline() {
	d := time.Now().Add(5000 * time.Millisecond)
	ctx, cancel := context.WithDeadline(context.Background(), d)

	// Even though ctx will be expired, it is good practice to call its
	// cancelation function in any case. Failure to do so may keep the
	// context and its parent alive longer than necessary.
	defer cancel()
	fmt.Println("block")
	select {
	case <-time.After(6 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
		fmt.Println("deadline")
	}
	fmt.Println("block out")
}

超时关闭:是具体时间关闭

定时关闭:是多长时间段(time duration)关闭

48. time

  • 定时触发
// time.AfterFunc 在duration时间过后,触发func,
time.AfterFunc(next.duration, func() {
    if !next.dateTime.IsZero() {
        for {
            if time.Now().Unix() >= next.dateTime.Unix() {
                break
            }
        }
    }
    s.run(job)
    time.AfterFunc(next.duration, func() {})	//触发完后,在加一个定时任务
})

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值