Golang 小结

Golang 的特点

  • 自带 GC
  • 静态编译
  • 语法简洁,天然支持并发,拥有同步并发的 channel 类型
  • 源码以".go"结尾
  • 函数、变量、常量、自定义类型、包(package)的命名遵循以下原则
    • 首字符可以是任意的 Unicode 字符或下划线
    • 剩余字符可以是 Unicode 字符、下划线或数字
    • 字符长度不作限制
    • 大小写敏感
    • private:声明在函数内部,是函数的本地值
    • protect:声明在函数外部,是对当前包可见(包内所有的".go"文件都可见)的全局值
    • public:声明在函数外部且首字母大写,是对所有包都可见的全局值
  • 四种声明方式
    • var (声明变量)
    • const (声明常量)
    • type (声明类型)
    • func (声明函数)
  • Go 工程中主要包含以下三个目录
    • src:源代码文件
    • pkg:包文件
    • bin:相关 bin 文件

Golang 的类型和函数

  • 值类型
  • 引用类型
    • slice:切片
    • map:映射
    • chan:管道
  • init 函数
    • 用于包(package)的初始化
    • 每个包(package)中可以拥有多个 init 函数
    • 包(package)的每个源文件也可以拥有多个 init 函数
    • 同一个包(package)中,多个 init 函数的执行顺序没有明确定义
    • 不同包(package)的 init 函数,按照包(package)导入的依赖关系决定该初始化函数的执行顺序
    • init 函数不能被其他函数所调用,是在 main 函数执行之前自动被调用
  • main 函数
    • 主函数:func main() {}
  • init 函数和 main 函数
    • 两个函数在定义时都不能有任何参数或返回值,且由程序自行调用
    • init 函数可以应用于任意包中,且可以重复定义
    • main 函数只能应用于 main 包中,且只能定义一个
    • 同一个 go 文件中,init 函数的调用顺序从上至下
    • 同一个 package 中不同的文件,按文件名字符串比较,从小到大顺序调用各个文件中 init 函数
    • 不同的 package,如果不相互依赖,按 main 包中"先 import 后调用"的顺序调用其包中的 init 函数;如果存在依赖,则先调用最早被依赖的 package 中的 init 函数,最后再调用 main 函数
    • 特例:如果在 init 函数中使用了 println() 或 print(),这两个函数在真正执行过程中不会按顺序执行,官方只推荐在测试环境中使用

Golang 的常用命令

  • go env:打印环境信息
  • go run:编译并运行源码文件
  • go get:从远程版本库中安装 Go 包及其依赖项
  • go build:编译源码文件
  • go install:将本地源码包编译并安装到工作空间的对应位置
  • go clean:删除执行其他命令时产生的文件和目录
  • go doc:查看 Go 文档
  • go test:进行代码测试
  • go list:列出代码包的信息
  • go fix:将指定代码包的所有 Go 源码文件中的旧版本代码修正为新版本
  • go vet:检查 Go 源码中静态错误的简单工具
  • go tool pprof:Go 的一个性能分析工具,支持标准的性能分析格式,可以帮助开发者识别程序中的性能瓶颈,从而可以优化代码以提升程序性能

Golang 中的占位符"_"

  • Go 语言中的特殊标识符,用于忽略结果。例如结果可能返回某个值或两个值,但我们实际上不需要这个值或只需要一个(Go 中如果定义了变量却不使用,编译器会报错)
  • import 导入包时,该包下的文件里的所有 init 函数都会被执行。如果不需要导入整个包,而仅是希望执行 init 函数,可以使用 import _ 包路径,这样仅调用 init 函数,且无法再通过包名来调用包中的其他函数

Golang 中的变量与常量

  • 变量
    • 声明后才能使用,同一作用域中不可重复声明,且声明后必须使用
    • var 变量名 变量类型,行尾无需分号,支持批量声明 var ()
    • 变量声明时,会自动对变量对应的内存区域做初始化操作,变量会被初始化成变量类型的默认值(整形、浮点型变量的默认值为 0,字符串变量的默认值为空字符串,布尔值变量的默认值为 false,切片/函数/指针变量的默认值为 nil),也可在声明时指定初始值
    • 变量类型可以省略,由编译器根据等号右边的值自行推导变量类型并完成初始化,支持 := 的短变量声明方式(短变量声明方式不可用于函数外)
    • 使用占位符"_"声明匿名变量,匿名变量不占用命名空间,不会分配内存,匿名变量之间不存在重复声明
  • 常量
    • 类似于变量声明,用 const 表示
  • iota
    • Go 中的常量计数器,只能用于常量表达式中
    • 在 const 关键字出现时将被重置为 0

Golang 中的数据类型

  • 布尔值
    • 以 bool 类型进行声明,默认值为 false
    • 不允许将整形强转为布尔型
    • 无法参与数值运算,也无法与其他类型进行转换
  • 字符串
    • uint8(byte):代表 ASCII 码中的一个字符
    • rune(int32):代表一个 UTF-8 字符,用于处理中文、日文或其他复合字符
    • 字符串底层是一个 byte 数组,所以字符串的长度是 byte 字节的长度;因为在 UTF-8 编码下一个中文汉字由 3 ~ 4 个字节组成,所以不能按字节去遍历,rune 类型用来表示 UTF-8 字符,它由一个或多个 byte 组成
    • 字符串不能直接修改,如果需要修改字符串,需要先将其转换为 []byte 或 []rune,再转换为 string,两种方式都会重新分配内存并复制字节数组(Go 中只有强制类型转换,没有隐式类型转换)
    func changeString() {
    	str1 := "hello world"
    	// 强制类型转换
    	byteStr1 := []byte(s1)
    	byteStr1[0] = 'H'
    	fmt.Println(string(byteStr1))
    
    	str2 := "你好世界"
    	runeStr2 := []rune(str2)
    	runeStr2[0] = '您'
    	fmt.Println(string(runeStr2))
    }
    
  • 数组
    • var a [len]int,数组长度必须是常量,且是数组类型的组成部分,一旦定义不可改变,var a [5]int 和 var a [10]int 是不同的类型
    • 值类型,赋值和传参会复制整个数组而不是指针,改变副本的值不会影响到本体的值
    • 支持 ==!=,因为内存总是被初始化过的
    • 有指针数组([n] *T)和数组指针(*[n] T)
    // 全局
    var arr0 [5]int = [5]int{1, 2, 3}
    var arr1 = [5]int{1, 2, 3, 4, 5}
    var arr2 = [...]int{1, 2, 3, 4, 5, 6}
    // 长度为5的字符串数组, 其中定义了索引3和4的元素值
    var str = [5]string{3: "hello", 4: "world"}
    var arr3 [5][3]int
    // 第二维度不可用"..."
    var arr4 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8 ,9}}
    
    // 局部
    // 未初始化的元素值为0
    a := [3]int{1, 2}
    // 初始化定义元素值并确定数组长度
    b := [...]int{1, 2, 3, 4}
    c := [5]int{2: 100, 4: 200}
    d := [...]struct{
    	name string
    	age  uint8
    }{
    	// 可省略元素类型
    	{"user1", 10},
    }
    e := [2][3]int{{1, 2, 3}, {4, 5, 6}}
    f := [...][2]int{{1, 1}, {2, 2}, {3, 3}}
    
  • 切片
    • var 变量名 []类型,比如 var str []string
    • 切片(slice)并不是数组或者数组指针,它通过内部指针和相关属性引用数组片段,以实现变长
    • 切片是数组的一个引用,因此是引用类型,但其自身是结构体,值拷贝传递
    • 切片的长度可以改变,它是一个可变的数组
    • 切片的遍历方式和数组一致,一样可以用 len() 求长度,读写操作不能超过该限制
    • cap() 可以求切片的最大扩张容量,不能超过数组限制,即 0 <= len(slice) <= len(array),array 表示 slice 引用的数组
    • 如果 slice == nil,那么 len、cap 的结果都为 0
    • 如果超过 slice.cap 的限制,就会重新分配底层数组,通常以 2 倍的容量重新进行分配。在进行大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制的开销,或者初始化足够长的 len 属性,或者改用索引号进行操作,及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 回收异常
    var s1 []int
    s2 := []int{}
    // make()
    var s3 []int = make([]int, 0)
    var s4 []int = make([]int, 0, 0)
    s5 := []int{1, 2, 3}
    // 通过make()创建切片
    // len表示切片中元素的个数, cap表示切片的长度(切片可以容纳的最大元素数量)
    // 可省略cap, 默认cap == len
    var slice []type = make([]type, len)
    slice := make([]type, len)
    slice := make([]type, len, cap)
    // 从数组切片, 左闭右开
    arr := [5]int{1, 2, 3, 4, 5}
    var s6 []int
    s6 = arr[1:4]
    // 通过初始化表达式构造, 可使用索引值指定
    s7 := []int{0, 1, 2, 3, 4, 6: 666}
    // 元素类型为[]T
    data := [][]int{
    	[]int{1, 2, 3},
    	[]int{4, 5, 6},
    }
    // append追加, 向slice尾部追加新元素并返回新的slice对象
    var a = []int{1, 2, 3}
    var b = []int{4, 5, 6}
    c := append(a, b...)
    d := append(c, 7, 8, 9)
    

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • map
    • map[KeyType]ValueType,默认初始值为 nil,需要使用 make() 来分配内存 make(map[KeyType]ValueType, [cap])
    • 使用 range() 进行遍历,遍历的顺序和添加键值对的顺序无关
    scoreMap := make(map[string]int, 8)
    scoreMap["xiaoming"] = 100
    
    userInfo := map[string]string{
    	"name": "xiaoming",
    	"address": "xxx",
    }
    
    // 判断是否存在某个键
    value, ok := map[key]
    
    // map的有序输出
    map1 := make(map[int]string, 5)
    map1[1] = "xiaoming"
    map2[2] = "xiaohong"
    slice1 := []int{}
    for k, _ := range map1 {
    	slice1 = append(slice1, k)
    }
    sort.Ints(slice1)
    for i := 0; i < len(map1); i++ {
    	fmt.Println(map1[slice1[i]])
    }
    

Golang 中的指针

  • 指针不能参与偏移和运算,是安全指针,&(取地址),*(根据地址取值)
  • ptr := &v,v 代表被取地址的变量,类型为 T;ptr 用于接收地址的变量,类型为 *T,也称为 T 的指针类型,* 代表指针
  • 在对普通变量使用 & 操作符取地址后会获得这个变量的指针,然后可以对指针使用 * 操作,即指针取值
    • 对变量进行取地址(&)操作,可以获得这个变量的指针变量
    • 指针变量的值是指针地址
    • 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值
  • 当一个指针被定义后没有分配到任何变量时,它的值为 nil,即为空指针
  • Go 中对于引用类型的变量,在使用时不仅需要声明,还需要为它分配对应的内存空间,否则我们无法存储值;而值类型的声明不需要分配,因为它们在声明的时候就已经默认分配好了内存空间;使用 new 或 make 来分配内存
    • func new(Type) *Type,Type 表示类型,new 函数只接受一个参数;*Type 表示类型指针,new 函数返回一个指向该类型内存地址的指针,并且该指针对应的值为该类型的零值
    • func make(t Type, Size ...IntegerType) Type,make 只用于 slice、map、chan 的内存创建,并且返回的类型就是这三种类型本身,而不是它们的指针类型(因为这三种类型本身就是引用类型,所以就没必要再返回它们的指针了)
	a := 10
	// 取变量a的地址, 将指针保存到b中
	b := &a
	// 指针取值, 根据指针去内存取值
	c := *b
	// 空指针
	var p *string
	// *d == 0
	d := new(int)
	// *e == false
	e := new(bool)
	// 声明指针变量f并初始化
	var f *int
	f = new(int)
	*f = 10
	// 声明map类型的变量g
	var g map[string]int
	g = make(map[string]int, 10)
	g["age"] = 10

在这里插入图片描述

Golang 中的结构体

  • 自定义类型
    • type MyInt int,通过 type 关键字的定义,定义一个具有 int 特性的新类型 MyInt
  • 类型别名
    • type byte = uint8type rune = int32
  • 结构体
    • type 类型名 struct { 字段名 字段类型 },类型名是标识自定义结构体的名称,在同一个包内不可重复;结构体中的字段名必须唯一;相同类型的字段可以写在同一行
    • 结构体实例化后才会真正地分配内存,才能使用结构体中的字段,结构体本身也是一种类型,声明结构体类型 var 结构体示例 结构体类型
    • 结构体允许其成员字段在声明时没有字段名而只有类型,称为匿名字段;匿名字段默认采用类型名作为字段名,因为结构体字段名称唯一,所以同一个结构体中同种类型的匿名字段唯一
    • 一个结构体中可以嵌套另一个结构体或结构体指针;当访问结构体成员时,优先会在结构体中查找该字段,如果找不到,再去匿名结构体中查找;嵌套结构体内部可能存在同名字段,所以在使用时需要指定具体的内嵌结构体的字段;可以通过嵌套匿名结构体来实现继承
    • 结构体中字段名称大小写敏感,大写开头表示可公开访问,小写开头则表示私有(仅在定义当前结构体的包中可访问)
    • key1:"value1" key2:"value2",结构体标签 Tag,是结构体的元信息,可以在运行时通过反射机制读取;结构体标签必须严格遵守键值对规范,中间不可存在空格
	// 自定义类型
	type MyInt int
	// 类型别名
	type CurInt = int
	
	func main() {
		var a MyInt
		var b CurInt
		
		// a的类型为main.MyInt, 表示main包下定义的MyInt类型, MyInt类型只会在代码中存在, 在编译完成时并不会有MyInt类型
		fmt.Printf("type of a:%T\n", a)
		// b的类型为int
		fmt.Printf("type of b:%T\n", b)
	}
	
	type person struct {
		name, address string
		age           int8
	}
	
	// 匿名结构体
	var user struct{Name string; Age int}
	// 指针类型结构体, 通过new关键字对结构体进行实例化, 得到结构体的地址
	var p2 = new(person)
	// 使用&对结构体进行取地址相当于new实例化
	// p3.name = "小明"等于(*p3).name = "小明"
	p3 := &person{}
	// 结构体键值对初始化, 没有指定初始值的字段默认为该字段类型的零值
	p4 := person{
		name: "xiaohong",
		age: 10,
	}
	// 简写初始化结构体
	// 必须初始化结构体中所有的字段
	// 初始值的填充顺序必须与字段在结构体中的声明顺序保持一致
	// 不能和键值对初始化方式混用
	p5 := &person{
		"xiaoming",
		"sz",
		10,
	}
	
	// 结构体标签
	type Student struct {
		// 通过指定tag实现json序列化该字段时的key, 即输出为{"id":1}
		ID int `json:"id"`
	}

Golang 中的方法及其接收者

  • Go 中的方法(method)是一种作用于特定类型变量的函数,这种特定类型变量成为接收者(Receiver),类似于其他语言中的 this 或 self
  • func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {},接收者变量的命名官方建议取接收者类型名的第一个小写字母;接收者类型和参数类似,可以是指针类型和非指针类型;方法名、参数列表、返回参数的格式与函数定义相同
  • 方法与函数的区别在于,函数不属于任何类型,而方法只属于特定的类型;对于普通函数,接收者为值类型时,不能用指针类型的数据直接传递,反之亦然;对于方法,接收者为值类型时,依然可以直接用指针类型的变量调用方法,反之亦然
  • 所有给定类型的方法属于该类型的方法集;类型 T 方法集包含全部 receiver T 方法;类型 *T 方法集包含全部 receiver T + *T 方法;如果类型 S 中包含匿名字段 T,则 S 和 *S 方法集包含 T 方法;如果类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T + *T 方法;嵌入 T 或 *T,*S 方法集总是包含 T + *T 方法
  • 指针类型的接收者
    • 需要修改接收者中的值
    • 接收者是拷贝代价比较大的大对象
    • 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者
  • 接收者的类型可以是任意类型,也可以为自定义类型添加方法,但是不能给其他包的类型定义方法
	type Person struct {
		name string
		age  int8
	}
	
	// 构造函数
	func NewPerson(name string, age int8) *Person {
		return &Person{
			name: name,
			age:  age,
		}
	}
	
	// Person的方法
	func (p Person) Dream() {
		fmt.Println("%s想暴富\n", p.name)
	}
	
	// 指针类型的接收者, 调用方法时修改接收者指针的任意成员变量, 在方法结束后修改仍有效
	func (p *Person) SetAge(newAge int8) {
		p.age = newAge
	}
	
	// 值类型的接收者, 代码运行时会将接收者的值复制一份, 修改操作只是针对的副本, 无法修改接收者变量本身
	func (p Person) SetName(newName string) {
		p.name = newName
	}
	
	func main() {
		p1 := NewPerson("xiaoming", 10)
		p1.Dream()
	}

Golang 中的条件语句

  • if
    • 可省略括号
    • 不支持三目运算
  • switch
    • 分支表达式可以是任意类型,不局限于常量
    • 可省略 break,默认会自动终止
    • 匹配成功后不会再自动向下执行其他 case,而是直接跳出整个 switch,可以使用 fallthrough 强制执行后面的 case
  • select
    • 类似于 switch,但 select 会随机执行一个可运行的 case;如果都不可运行,优先执行 default 子句,同时程序的执行会从 select 语句后的语句中恢复;如果没有 default 子句,则将阻塞至有 case 可以运行为止
    • 每个 case 必须是一个通信操作(channel),要么是发送,要么是接收
    • 用途:超时判断、协程退出、判断 channel 是否阻塞
  • for … range
    • range 会复制对象
    • for 可以遍历 array 和 slice、key 为整形递增的 map、string;for … range 除了可以完成所有 for 可以完成的事情,还可以遍历 key 为 string 类型的 map 并同时获取 key 和 value、遍历 channel
  • Goto、Break、Continue
    • 循环控制语句,可以配合标签(label)使用;标签名区分大小写,定义后必须使用
    • goto 语句可以无条件地转移到程序中指定的行,它使程序能够从一个地方跳转到另一个地方,而不需要经过中间的代码
    • break 语句可以终止循环吗,它使程序能够从一个循环中跳出,而不再执行该循环余下的代码
    • continue 语句用于跳过当前循环余下的代码,并直接开始下一次
    // 这段代码使用goto语句创建了一个循环, 它会打印出从0开始的10个数字(0-9), 它会首先打印i的值, 然后i的值加1, 如果i的值小于10, 它会跳回到Loop, 重复上述步骤
    func main() {
    	i := 0
       Loop:
    	fmt.Println(i)
    	i++
    	if i < 10 {
    		goto Loop
    	}
    }
    

Golang 中的 defer 和 recover

  • defer
    • 关键字,用于延迟函数的执行,直到函数返回前;用于确保函数可以在 return 前完成一些清理操作,如:关闭文件、释放资源等
    • 多个 defer 语句,按先进后出的方式执行,后面的语句会依赖前面的资源,因此如果前面的资源先释放了,那么后面的语句就无法再执行了
    • defer 语句中的变量,在 defer 声明时就决定了
  • recover
    • Go 没有结构化异常,使用 panic 抛出错误,使用 recover 捕获错误
    • 如果函数中存在 panic 语句,则会终止其后要执行的代码;如果函数中存在 defer 语句,则按 defer 的逆序执行
    • 使用 recover 处理 panic,defer 必须放在 panic 之前定义,并且 recover 只能在 defer 调用的函数中才有效
    • recover 处理异常后,函数不会恢复到 panic 的位置,而是恢复到 defer 之后的位置
    func test() {
    	defer func() {
    		if err := recover(); err != nil {
    			println(err.(string))
    		}
    	}()
    
    	panic("panic error")
    }
    
    // 实现类似try...catch的异常处理
    func Try(fun func(), handler func(interface{})) {
    	defer func() {
    		if err := recover(); err != nil {
    			handler(err)
    		}
    	}()
    	
    	fun()
    }
    
    func main() {
    	Try(func() {
    		panic("panic error")
    	}, func(err interface{}) {
    		fmt.Println(err)
    	})
    }
    

Golang 中的接口

  • Go 中的接口是一种虚拟类型,它不定义任何变量,它只是一系列方法的声明,这些方法构成了接口的签名。接口定义了一组方法,但没有实现它们,它只是定义某种行为的契约。如果类型实现了接口定义的所有方法(指具有相同名称、参数列表不包括参数名,以及返回值),那么它就实现了这个接口
  • 接口提供了一种结构化编程的方法,将复杂的程序结构化,使其易于维护和扩展。接口的使用允许开发者定义类型的行为,而不需要实现它
  • 接口也可以用于实现多态,它允许我们通过抽象层次来定义程序,而不用考虑实际实现细节。它使程序更具灵活性,可以从不同的实现中选择最合适的实现
  • 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个副本的指针,既无法修改副本的状态,也无法获取指针
  • 只有当接口存储的类型和对象都为 nil 时,接口才等于 nil
  • 一个类型可以实现多个接口
  • 接口命名习惯以 er 结尾
  • 当方法名首字母大写且接口类型名首字母大写时,该方法可被接口所在包之外的代码访问
  • 注意 值接收者实现接口指针接收者实现接口 的区别:使用值接收者实现接口后,结构体和结构体指针类型的变量都可以直接赋值给该接口变量;使用指针接收者实现接口后,不可以接收结构体类型的变量
  • 空接口 var x interface{} 是指没有定义任何方法的接口,因此任何类型都实现了空接口;空接口类型的变量可以存储任意类型的变量

Golang 中的 goroutine 和 GMP 调度

  • goroutine
    • 在调用函数前加上关键字 go,就能开启一个 goroutine,函数与 goroutine 的对应关系为 1:N
    • 注意在程序启动时会自动为 main 函数创建一个默认的 goroutine,当 main 函数返回的时候该 goroutine 就结束了,并且其内部启动的其他 goroutine 也会一同结束
    • goroutine 的调度是随机的
  • GMP
    • GMP 是 Go 语言运行时(runtime)层面的实现,是 Go 自己实现的一套调度系统,区别于操作系统调度 OS 线程
    • G 很好理解,即 goroutine,里面除了存放本 goroutine 的信息外,还存放了与所在 P 的绑定等信息
    • P 是管理着一组 goroutine 的队列,P 里面会存储当前 goroutine 运行的上下文环境(函数指针、堆栈地址以及地址边界),P 会对自己管理的 goroutine 队列做一些调度(比如把占用 CPU 时间较长的 goroutine 暂停,运行后续的 goroutine 等等),当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了则会去其他的 P 队列里抢任务
    • M(machine)是 Go 运行时(runtime)对操作系统内核线程的虚拟,M 与内核线程一般是一对一映射的关系,一个 groutine 最终是要放到 M 上执行的
    • P 与 M一般也是一对一的映射关系,P 管理着一组 G 挂载在 M 上运行,当一个 G 长久阻塞在一个 M 上时,runtime 会新建一个 M,阻塞 G 所在的 P 会把其他的 G 挂载在新建的 M 上,当旧的 G 阻塞完成或者认为其已经死掉时,再回收旧的 M
    • P 的个数可以通过 runtime.GOMAXPROCS 设定(最大 256,Go 1.5 版本之后默认为物理线程数), 在并发量大的时候会增加一些 P 和 M,但不会太多,因为切换太频繁会得不偿失
    • 单从线程调度而言,Go 相比其他语言的优势在于 OS 线程是由 OS 内核来调度的,而 goroutine 则是由 Go 运行时(runtime)自己的调度器来调度的,这个调度器使用一个称为 m:n 调度的技术(复用/调度 m 个 goroutine 到 n 个 OS 线程上)。 其一大特点是 goroutine 的调度是在用户态下完成的,不会涉及到内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池,不直接调用系统的 malloc 函数(除非内存池需要改变),成本比调度 OS 线程低很多;另一方面是它充分利用了多核的硬件资源,近似地把若干个 goroutine 均分在物理线程上,再加上本身 goroutine 的超轻量特性(OS 线程一般都有固定的栈内存(通常为 2 MB),一个 goroutine 在其生命周期开始时只有很小的栈(典型情况下为 2 KB),goroutine 的栈不是固定的,它可以按需进行增缩,goroutine 的栈大小限制可以达到 1 GB,所以在 Go 语言中一次性创建十万左右的 goroutine 也是可以的),从而保证了调度方面的性能
  • https://www.topgoer.cn/docs/golang/chapter09-11

Golang 中的 channel

  • 用于在不同 goroutine 之间进行同步和交换数据,它可以视为一种管道,在管道的一端可以放入数据,在另一端可以取出数据,遵循先进先出的规则;channel 中只能存储特定类型的数据,而且必须在使用前先建立,否则会出现 panic 错误;channel 可以是双向的,允许 goroutine 之间双方发送和接收数据,也可以是单向的,只允许其中一个 goroutine 发送或者接收数据
  • channel 也是一种阻塞的数据结构,当 goroutine 将数据写入 channel 时,直到另一个 goroutine 将数据读出来,这个 goroutine 才会继续执行
  • var 变量 chan 元素类型,空值为 nil;声明通道必须使用 make 函数初始化空间后才能使用 make(chan 元素类型, [缓冲大小]),缓冲大小可选;无缓冲的通道是阻塞型通道,必须有接收才能发送;有缓冲的通道在缓冲大小填充满后进入阻塞状态
  • 关闭已经关闭了的 channel 也会引发 panic
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

参考资料

  • https://www.topgoer.cn/docs/golang/golang-1ccjbpfstsfi1
  • https://www.yuque.com/aceld
  • https://www.bilibili.com/video/BV1gf4y1r79E/?spm_id_from=333.880.my_history.page.click&vd_source=934085b00c3c6e4153bf6e8a7a80beb8
  • https://juejin.cn/post/6844904122450182151

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值