Go语言小记

Go语言小记

fmt.Sprintf("%+v",struct)  // + 号可打印出key值
package main 
func main(){
	x :=100
	println(&x)
    x,y :=200,"abc"  //x退化为赋值操作,仅y是定义变量
	println(&x,x)
	println(y)
}
//0xc000049f68
//0xc000049f68 200  
//abc 
//退化赋值的前提条件是:最少有一个新变量被定义,其必须是同一作用域
package main
func main(){
	x ,y := 1,2
	x, y =y+3,x+2
	println(x,"  ",y)
}//5    3
//在进行多变量赋值操作时,首先计算出所有的右值,然后在依次完成赋值操作
  1. 在常量组中如不指定类型和初始值,则与上一行非常量右值()表达式文本相同
package main
import "fmt"
func main() {
	const (
		x int16 = 120
		y
		z
		s = "abc"
		d
	)
	fmt.Printf("%T ,%v\n",y,y)
	fmt.Printf("%T ,%v\n",z,z)
	fmt.Printf("%T ,%v\n",d,d)
}
// int16 ,120
// int16 ,120
// string ,abc

  1. 常量不能取值,而是被作为指令数据展开

  2. 标准库maht定义了各数字类型的取值范围

  3. 标准库strconv可在不同进制(字符串)间转换

package main
import "strconv"
func main() {
	a, _ := strconv.ParseInt("1100100", 2, 32)
	b, _ := strconv.ParseInt("0144", 8, 32)
	c, _ := strconv.ParseInt("64", 16, 32)
	println(a,b,c)
	println(strconv.FormatInt(a,2))
	println(strconv.FormatInt(a,8))
	println(strconv.FormatInt(a,16))
}
//100 100 100
//1100100
//144
//64
  1. 浮点数需注意小数位的有效精度

  2. 别名 byte is uint8, rune is int32

  3. 引用函数必须使用make()函数来创建,引用类型包括(map ,slice , channel)

  4. 无显示的常量在参与运算时会自动转换类型

  5. 自增,自减不再是运算符,只能作为独立语句,不能用于表达式,

  6. 指针类型支持相等运算符,但不能做加减法运算和类型转换,如果指针指向同一地址,或都为nil,那么他们相等

  7. switch 支持语句初始化

package main 
func main(){
	switch x := 5;x {
		default :   //编辑器确保不会先执行default
		x += 100
		println(x)
	case 5:
		x += 50
		println(x) 
	}
}
//55
  1. switch 隐式brack,如需贯通,可使用fallthrough

  2. switch可用来替换if语句,被省略的条件表达式默认值为true,继而匹配

package main 
func main(){
	switch x := 5; {
	case x > 5 :
		println("a")
		case x > 0 && x <= 5:
			println("b") 
		default :
		println("z")
		}
}
//b
  1. 在for循环中,初始化语句仅执行一次,

  2. 函数只能判断是否为nil,不支持其他比较操作

  3. 变参本质上就是一个切片,只能接收一到多个同类型参数,且必须放在列表尾部

package main
import "fmt"
func test(s string, a ...int) {
	fmt.Printf("%T ,%v\n", a, a)
}
func main(){
	test("abc",1,2,3,4)
}
//[]int ,[1 2 3 4]
  1. 有返回值的函数,必须有明确的return终止语句
  2. 闭包
package main 
func test(x int) func(){
	return func (){
		println(x)
	}
}
func main(){
	f := test(123)
	f()
}
//123
package main 
func test(x int) func(){
	println(&x)
	return func (){
		println(&x,x)
	}
}
func main(){
	f := test(123)
	f()
}
//0xc000049f68
//0xc000049f68 123
  • for循环里的初始值是i,复用局部变量,故读的是最后一次地址和值
package main
func test() []func() {
	var s []func()
	for i := 0; i < 2; i++ {
		s = append(s, func() {
			println(&i, i)
		})
	}
	return s
}
func main() {
	for _, f := range test() {
		f()
	}
}
//0xc000088000 2
//0xc000088000 2
  • 解决方法
package main

func test() []func() {
	var s []func()
	for i := 0; i < 2; i++ {
		x := i
		s = append(s, func() {
			println(&x, x)
		})
	}
	return s
}
func main() {
	for _, f := range test() {
		f()
	}
}
//0xc000014040 0
//0xc000014048 1
  1. 延迟调用 defer
package main 
func main(){
	x,y := 1,2
	defer func (a int){
		println("defer x,y = ",a,y) //y为闭包引用
	}(x)							//注册时复制调用参数
	x +=100							//对x的修改不会影响延迟调用
	y +=200
	println(x,y)
}
//101 202
//defer x,y =  1 202
  • FILO次序
package main 
func main(){
	defer println("a")
	defer println("b")
}
//b
//a
  • defer的运行要在return之后
package main 
func test()(z int){
	defer func (){
		println("defer :",z)
		z += 100
	}()
	return 100
}
func main(){
	println("test: ",test())
}
//defer : 100
//test:  200
  • 延迟调用在函数结束时才被执行,在main函数中尽量不用defer,在对算法要求高且压力大的不用defer
  1. error 尽量使用返回错误,而非文本
package main
import (
	"errors"
	"log"
)
var errDivByZero = errors.New("not zero")
func div(x, y int) (int, error) {
	if y == 0 {
		return 0, errDivByZero
	}
	return x / y, nil
}
func main() {
	z, err := div(5, 0)
	if err != nil {
		log.Fatalln(err)
	}
	println(z)
}
//2022/07/05 09:38:46 not zero
//exit status 1
package main
import (
	"fmt"
	"log"
)
type DivError struct {
	x, y int
}
func (DivError) Error() string {
	return "divsion by zero"
}
func div(x, y int) (int, error) {
	if y == 0 {
		return 0, DivError{x, y}
	}
	return x / y, nil
}
func main() {
	z, err := div(5, 0)
	if err != nil {
		switch e := err.(type) {
		case DivError:
			fmt.Println(e, e.x, e.y)
		default:
			fmt.Println(e)
		}
		log.Fatalln(err)
	}
	println(z)
}
//divsion by zero 5 0
//2022/07/05 09:47:13 divsion by zero
//exit status 1
  • panic/recover 类似try/catch
package main
import "log"
func test() {
	defer println("test.1")
	defer println("test.2")
	panic("i am dead")
}
func main() {
	defer func() {
		log.Println(recover())
	}()
	test()
}
//test.2
//test.1
//2022/07/05 10:00:43 i am dead
  • 连续调用panic,仅最后一个会被recover捕获
package main
import "log"
func main() {
	defer func() {
		for {
			if err := recover(); err != nil {
				log.Println(err)
			} else {
				log.Fatalln("fatal")
			}
		}
	}()
	defer func() {
		panic("you")
	}()
	panic("my")
}
//2022/07/05 10:08:21 you
//2022/07/05 10:08:21 fatal
  • recover必须在延迟调用函数中执行才能正常工作
package main
import "log"
func catch() {
	log.Println("catch 1", recover())
}
func main() {
	defer catch()						//捕获
	defer log.Println("2", recover())	//失败
	defer recover()						//失败
	panic("i am dead")
}
//2022/07/05 10:12:15 2 <nil>
//2022/07/05 10:12:15 catch 1 i am dead
  • 除非是不可恢复性,导致系统无法正常工作的错误,否则不建议使用panic
  1. 字符串默认值不是nil,而是 ""
package main
func main() {
	var s string
	println(s == "") //true
}
  • 使用 定义 ``"可以跨行,而""不可以
package main
func main() {
	s := `line\r\n,
	line 2`
	println(s)
}
//line\r\n,
//line 2
  • 字符串支持 != , == , < , > , + , += 运算符
package main

func main() {
	s := "ab" +        //跨行时 + 必须放在末尾
		"cd"
	println(s == "abcd") //true
	println(s > "abc")   //true
}
  • 允许以索引号访问字节数组(非字符),但不能获取元素地址
package main 
func main(){
	s := "abc"
	println(s[1])  //98   b
	//println(&s[1])    // error 错误
}
  • 以切片语法(起始和结束索引号)返回子串时,其内部依旧指向原字节数组
package main
import (
	"fmt"
	"reflect"
	"unsafe"
)
func main() {
	s := "abcdefg"
	s1 := s[:3]  //从头开始到指定的位置
	s2 := s[1:4] //从1 开始,到指定的位置
	s3 := s[2:]  //从2 开始到结束的位置
	println(s1, s2, s3)
	//reflect.StringHeader和 string头部结构相同
	//unsafe.Pointer 用于指针类型转换
	fmt.Printf("%#v \n", (*reflect.StringHeader)(unsafe.Pointer(&s)))
	fmt.Printf("%#v \n", (*reflect.StringHeader)(unsafe.Pointer(&s1)))
}
//abc bcd cdefg
//&reflect.StringHeader{Data:0x455c89, Len:7}
//&reflect.StringHeader{Data:0x455c89, Len:3}
  • 使用for遍历字符串时,分为 byte 和 rune两种方式
package main
import (
	"fmt"
)
func main() {
	s := "安安"
	for i := 0; i < len(s); i++ { //byte
		fmt.Printf("%d : [%c]\n", i, s[i])
	}
	for index, value := range s { //返回索引,以及unicode字符
		fmt.Printf("%d : [%c]\n", index, value)
	}
}
// 0 : [å]
// 1 : [®]
// 2 : []
// 3 : [å]
// 4 : [®]
// 5 : []
// 0 : [安]
// 3 : [安]
  • 用append函数,可将string直接追加到[]byte内
package main
import "fmt"
func main() {
	var bs []byte
	bs = append(bs, "abc"...) //...
	fmt.Println(bs)  //[97 98 99]
}
  1. 数组
  • 对于结构等复合类型,可省略元素初始化类型标签
package main

import "fmt"

func main() {
	type user struct {
		name string
		age  byte
	}
	d := [...]user{
		{"Tom", 20},
		{"Mary", 18},
	}
	fmt.Printf("%#v\n",d)
}
//[2]main.user{main.user{name:"Tom", age:0x14}, main.user{name:"Mary", age:0x12}}
  • 在定义多维数组时,仅第一维度允许使用 "..."
package main
import "fmt"
func main() {
	a := [2][2]int{
		{1, 2},
		{3, 4},
	}
	b := [...][2]int{
		{1, 2},
		{3, 4},
	}
	c := [...][2][2]int{
		{
			{1, 2},
			{3, 4},
		},
		{
			{10, 20},
			{30, 40},
		},
	}
	fmt.Println(a) //[[1 2] [3 4]]
	fmt.Println(b) //[[1 2] [3 4]]
	fmt.Println(c) //[[[1 2] [3 4]] [[10 20] [30 40]]]
}
  • 内置函数len 和 cap都返回第一维度长度
package main
func main(){
	a := [2]int{}
	b := [...][2]int{
		{10,20},
		{30,40},
		{50,60},
	}
	println(len(a),cap(a))  //2 2
	println(len(b),cap(b))  //3 3
	println(len(b[1]),cap(b[1])) //2 2
}
  • 如元素类型支持 == , != 操作符,数组也支持此操作
package main
func main() {
	var a, b [2]int
	println(a == b)    // true
	c := [2]int{1, 2}
	d := [2]int{0, 1}
	println(c != d)    //true
	//var e, f [2]map[string]int  
	//println(e == f)   //error
}
  • 要分清指针数组和数组指针的区别.指针数组是指元素为指针类型的数组,数组指针是获取数组变量的地址
package main
import "fmt"
func main() {
	x, y := 10, 20
	a := [...]*int{&x, &y}	   //元素为指针的指针数组
	p := &a					   //存储数组地址的指针
	fmt.Printf("%T,%v\n", a, a)  //[2]*int,[0xc000014098 0xc0000140b0]
	fmt.Printf("%T,%v\n", p, p)  //*[2]*int,&[0xc000014098 0xc0000140b0]
}
  • 数组指针可直接操作元素
package main
func main() {
	a := [...]int{1, 2}
	p := &a
	p[1] += 10

	println(p[1])  //12
}
  • Go数组是值类型,赋值和传参操作都会复制整个数组数据,如果需要,可改用指针或切片,以此避免数据复制
package main
import "fmt"
func test(x *[2]int) {
	fmt.Printf("x: %p,%v\n", x, *x)  //x: 0xc0000140b0,[10 20]
	x[1] += 100
}
func main() {
	a := [2]int{10, 20}
	test(&a)
	fmt.Printf("a : %p ,%v\n", &a, a) //a : 0xc0000140b0 ,[10 120]
}
  1. 切片
  • 切片本身并非动态数组或数组指针,它内部通过指针引用底层数组,设定相关属性将数据读写操作限定在指定区域内
package main
import "fmt"
func main() {
	s1 := make([]int, 3, 5)    //指定len,cap底层数组初始化为零值
	s2 := make([]int, 3)       //省略cap和len相等
	s3 := []int{10, 20, 5: 25} //按初始化元素分配底层数组,并设置len cap
	fmt.Println(s1, len(s1), cap(s1)) //[0 0 0] 3 5
	fmt.Println(s2, len(s2), cap(s2)) //[0 0 0] 3 3
	fmt.Println(s3, len(s3), cap(s3)) //[10 20 0 0 0 25] 6 6
}
  • 不支持比较操作,就算元素类型支持也不行,仅能判断是否为nil
package main
func main(){
	a := make([]int,1)
	b := make([]int,1)
	println(a == b)   //error
}
  • 元素类型也是切片,那么就可实现交错数组功能
package main
import "fmt"
func main() {
	x := [][]int{
		{1, 2},
		{10, 20, 30},
		{100}, //<--
	}
	fmt.Println(x[1])   //[10 20 30]
	x[2] = append(x[2], 200, 300)
	fmt.Println(x[2])   //[100 200 300]
}
  • 新建切片对象依旧指向原底层数组,也就是说修改对所有关联切片可见
package main
import "fmt"
func main() {
	d := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := d[3:7]
	s2 := s1[1:3]
	for i := range s2 {
		s2[i] += 100
	}
	fmt.Println(d)  //[0 1 2 3 104 105 6 7 8 9]
	fmt.Println(s1) //[3 104 105 6]
	fmt.Println(s2) //[104 105]
}
  • 数据被追加到原底层数组,如超出cap限制,则为新切片对象重新分配数组
    • 注意:
    • 是超出切片cap限制,而非底层数组长度限制,因为cap可小于数组长度
    • 新分配数组长度是原cap的2倍,而非原数组的2倍
    • 并非总是2倍,对于较大的切片,会尝试扩容1/4,以节约内存
package main
import "fmt"
func main() {
	s := make([]int, 0, 100)
	s1 := s[:2:4]
	s2 := append(s1, 1, 2, 3, 4, 5, 6)
	fmt.Printf("s1 : %p : %v\n", &s1[0], s1) //s1 : 0xc0000d6000 : [0 0]
	fmt.Printf("s2 : %p : %v\n", &s2[0], s2) //s2 : 0xc0000c2080 : [0 0 1 2 3 4 5 6]
	fmt.Printf("s data: %v\n", s[:10])	     //s data: [0 0 0 0 0 0 0 0 0 0]
	fmt.Printf("s1 cap : %d,s2 cap: %d\n", cap(s1), cap(s2)) //s1 cap : 4,s2 cap: 8
}
  • nil切片追加数据时,会为底层分配空间
package main
import "fmt"
func main() {
	var s []int
	s = append(s, 1, 2, 3)
	fmt.Println(s,"  ",cap(s)) //[1 2 3]    3
}
  1. 字典(map)
  • 作为无序键值对集合,字典要求key必须是支持相等运算符(== , !=)的数据类型,比如数字,字符串,指针,数组,结构体,以及对应接口类型

  • 字典是引用类型,使用make函数或初始化表达语句来创建

package main
import "fmt"
func main() {
	m := make(map[string]int)
	m["a"] = 1
	m["b"] = 2
	m2 := map[int]struct { //值为匿名结构类型
		x int
	}{
		1: {x: 100},
		2: {x: 200},
	}
	fmt.Println(m, m2) //map[a:1 b:2] map[1:{100} 2:{200}]
}
  • 不能对nil字典进行写操作,但却能读
  • 内容为空的字典,与nil是不同的
package main
func main() {
	var m1 map[string]int
	m2 := map[string]int{}
	println(m1 == nil, m2 == nil) //true false 
}
  • 字典在同一时间内只能有一个任务,如果某个任务正在对字典进行写操作,那么其他任务就不能对该字典执行并发操作(读,写 ,删除)否则会导致进程崩溃
package main
import "time"
func main() {
	m := make(map[string]int)
	go func() {
		for {
			m["a"] += 1
			time.Sleep(time.Microsecond)
		}											//写操作
	}()
	go func() {
		for {
			_ = m["b"]
			time.Sleep(time.Microsecond)
		}											//读操作
	}()
	select {}
}
  • 可用sycn.RWMutex 实现同步,避免读写操作同时进行
  1. 结构
package main
import "fmt"
type node struct {
	_    int
	id   int
	next *node
}
func main() {
	n1 := node{
		id: 1,
	}
	n2 := node{
		id:   2,
		next: &n1,
	}
	fmt.Println(n1 ,n2) //&{0 1 <nil>} {0 1 <nil>} {0 2 0xc000004078}
}
  1. 方法
package main
import "fmt"
type N int
func (n N) toString() string {
	return fmt.Sprintf("%#x", n)
}
func main(){
	var a N = 25
	println(a.toString())  //0x19
}
  • 方法同样不支持重载receiver 参数名没有限制,按惯例会选用简短有意义的名称(不推荐使用 this、self)如方法内部并不引用实例,可省略参数名,仅保留类型

  • 不能用多级指针调用方法

  1. 接口
  • 接口命名方式 以 er为后缀结尾
  1. 并发和并行的区别
  • 并发:逻辑上具备同时处理多个任务的能力

  • 并行:物理上在同一时刻执行多个并发任务

  1. 与defer一样,goroutine也会因"延迟执行"而立即计算并复制执行参数
package main
import "time"
var c int
func counter() int {
	c++
	return c
}
func main() {
	a := 100
	go func(x, y int) {
		time.Sleep(time.Second)
		println("go:",x,y)
	}(a,counter())
	a += 100
	println("main:",a,counter())
	time.Sleep(time.Second * 3)
}
//main: 200 2
//go: 100 1
  • 进程退出时不会等待并发任务结束,可用通道(channel)阻塞,然后发出推出信号
package main
import "time"
func main() {
	exit := make(chan struct{})
	go func() {
		time.Sleep(time.Second)
		println("goroutine done")
		close(exit)
	}()
	println("main ..")
	<-exit
	println("main exit.")
}
  • 如要等待多个任务结束,推荐使用sync.WaitGroup.通过设定计数器,让每个goroutine在退出前递减,直至归零时解除阻塞
package main
import (
	"sync"
	"time"
)
func main() {
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func (id int){
			defer wg.Done()
			time.Sleep(time.Second)
			println("goroutine",id ,"done.")
		}(i)
	}
	println("main ...")
	wg.Wait()
	println("main exit.")
}
  • 可在多处使用Wait阻塞,它们都能接收到通知
package main
import (
	"sync"
	"time"
)
func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	go func (){
		wg.Wait()
		println("wait exit.")
	}()
	go func (){
		time.Sleep(time.Second)
		println("done.")
		wg.Done()
	}()
	wg.Wait()
	println("main exit.")
}
//done.
//wait exit.
//main exit.
  • 运行时可能会创建很多线程,但任何时候仅有限的几个线程参与并发任务执行.该数量默认与处理器核数相等,可用runtime.GOMAXPROCS函数(或环境变量)修改
    • 如参数小于1,GOMAXPROCS仅返回当前设置值,不做任何调整
package main
import (
	"math"
	"runtime"
	"sync"
)
//测试目标函数
func count() {
	x := 0
	for i := 0; i < math.MaxUint32; i++ {
		x += i
	}
	println(x)
}
//循环执行
func test(n int){
	for i := 0; i < n; i++ {
		count()
	}
}
//并发执行
func test2(n int){
	var wg sync.WaitGroup
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func(){
			count()
			wg.Done()
		}()
	}
	wg.Wait()
}
func main(){
	n := runtime.GOMAXPROCS(0)  
    println(runtime.NumCPU())  //结果与处理器的核数有关
	//test(n)
	test2(n)
}
//9223372030412324865
//9223372030412324865
//9223372030412324865
//9223372030412324865
//9223372030412324865
//9223372030412324865
//9223372030412324865
//9223372030412324865
  • goroutine 任务无法设置优先级,无法获取编号,没有局部存储(TLS),没有返回值,但是除优先级外,其他功能都很容易实现
package main
import (
	"fmt"
	"sync"
)
func main() {
	var wg sync.WaitGroup
	var gs [5]struct {					//用于实现类似TLS功能
		id     int						//编号
		result int						//返回值
	}
	for i := 0; i < len(gs); i++ {
		wg.Add(1)
		go func(id int) {				//使用参数避免闭包延迟求值
			defer wg.Done()
			gs[id].id = id
			gs[id].result = (id + 1) * 100
		}(i)
	}
	wg.Wait()
	fmt.Printf("%+v\n", gs)
}
//[{id:0 result:100} {id:1 result:200} {id:2 result:300} {id:3 result:400} {id:4 result:500}]
  • GOsched 暂停,释放线程去执行其他任务.当前任务被放回队列,等待下次调度时恢复执行
package main
import "runtime"
func main() {
	runtime.GOMAXPROCS(1)
	exit := make(chan struct{})
	go func() {							//任务a
		defer close(exit)
		go func() {						//任务b,放在此处,是为了确保a优先执行
			println("b")
		}()
		for i := 0; i < 4; i++ {
			println("a:", i)
			if i == 1 {					//让出当前线程,调度执行b
				runtime.Gosched()
			}
		}
	}()
	<-exit
}
//a: 0
//a: 1
//b
//a: 2
//a: 3
  • Goexit立即终止当前任务,运行时确保所有已注册延迟调用被执行.该函数不会影响其他并发任务,不会引发panic,自然也就无法捕获
package main
import "runtime"
func main() {
	exit := make(chan struct{})
	go func() {
		defer close(exit)							//执行
		defer println("a")							//执行
		func() {
			defer func() {
				println("b", recover() == nil)		//执行,recover 返回nil
			}()
			func() {
				println("c")
				runtime.Goexit()					//立即终止整个调用堆栈
				println("c done.")					 //不会执行
			}()
			println("b done.")						//不会执行
		}()
		println("a done")							//不会执行
	}()
	<-exit
	println("main exit")
}
//c
//b true
//a
//main exit
  1. 通道
package main 
func main(){
	done := make(chan struct{})				//结束事件
	c := make(chan string)					//数据传输通道
	go func() {		
		s := <-c							//接收消息
		println(s)		
		close(done)							//关闭通道,作为结束通知
	}()
		c<-"hi!"							//发送消息
		<-done								//阻塞,直到有数据或管道关闭
}
//hi!
  • 同步模式必须有配对操作的goroutine 出现,否则会一直阻塞.而异步模式在缓冲区未满或数据未读玩前,不会阻塞,多数时候,异步通道有助于提升性能,减少排队阻塞
package main
func main(){
	c := make(chan int ,3)				//创建带3个缓冲槽的异步通道
	c <- 1								//缓冲区未满,不会阻塞
	c <- 2
	println(<- c)						//缓冲区尚有数据,不会阻塞
	println(<- c)
}
//1
//2
  • 缓冲区大小仅是内部属性,不属于类型组成部分.另外通道变量本身就是指针,可用相等操作符判断是否为同一对象或nil
package main
import (
	"fmt"
	"unsafe"
)
func main() {
	var a, b chan int = make(chan int, 3), make(chan int)
	var c chan bool
	println(a == b)
	println(c == nil)
	fmt.Printf("%p, %d\n", a, unsafe.Sizeof(a))
}
//false
//true
//0xc000110080, 8
  • 内置函数cap和len返回缓冲区大小和当前以缓冲数量,而对于同步通道则都返回0,据此可判断通道是同步还是异步
package main
func main(){
	a, b :=make(chan int),make(chan int,3)
	b<- 1
	b<- 2
	println("a:",len(a),cap(a))
	println("b:",len(b),cap(b))
}
//a: 0 0
//b: 2 3
  • 除使用简单的发送和接收操作符外,还可用 ok-idom 或range 模式处理数据
package main
func main() {
	done := make(chan struct{})
	c := make(chan int)
	go func() {
		defer close(done)
		for {
			x, ok := <-c
			if !ok {
				return
			}
			println(x)
		}
	}()
	c <- 1
	c <- 2
	c <- 3
	close(c)
	<-done
}
//1
//2
//3
  • 对于循环接受数据,range模式更简洁一些,(及时用close函数关闭通道引发结束通知,否则可能会导致死锁)
package main
func main() {
	done := make(chan struct{})
	c := make(chan int)
	go func() {
		defer close(done)
		for x := range c {			//循环获取消息,直到通道被关闭
			println(x)
		}
	}()
	c <- 1
	c <- 2
	c <- 3
	close(c)
	<-done
}
//1
//2
//3
  • 通知可以是群体性的,也未必就是通知结束,可以是任何需要表达的事件
  • 一次性事件用close效率更好,没有多余开销,连续或多样性事件,可传递不同数据标志实现,还可使用sync.Cond实现单播或广播事件
package main
import (
	"sync"
	"time"
)
func main() {
	var wg sync.WaitGroup
	ready := make(chan struct{})
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			println(id, ": ready.")
			<-ready
			println(id, ": running...")
		}(i)
	}
	time.Sleep(time.Second)
	println("Ready?Go!")
	close(ready)
	wg.Wait()
}
//2 : ready.
//1 : ready.
//0 : ready.
//Ready?Go!
//0 : running...
//1 : running...
//2 : running...
    • 向已关闭通道发送数据,引发panic
    • 从已关闭接收数据,返回以缓冲数据或零值
    • 无论收发,nil通道都会阻塞
package main 
func main(){
	c := make(chan int,3)
	c <-10
	c <-20
	close(c)
	for i := 0; i < cap(c)+1; i++ {
		x,ok := <-c
		println(i,":",ok,x)
	}
}
//0 : true 10
//1 : true 20
//2 : false 0
//3 : false 0
  • 通常使用类型转换来获取单向通道,并分别赋予操作双方
package main
import "sync"
func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	c := make(chan int)
	var recv <-chan int = c
	var send chan<- int = c
	go func() {
		defer wg.Done()
		for x := range recv {
			println(x)
		}
	}()
	go func() {
		defer wg.Done()
		defer close(c)
		for i := 0; i < 3; i++ {
			send <- i
		}
	}()
	wg.Wait()
}
//0
//1
//2
  • 不能在单向通道上做逆向操作

  • close不能用于接收端

  • 无法将单向通道重新转换回去

  • 如果同时处理多个通道,可选用select语句.它会随机选择一个可用通道作收发操作

package main
import "sync"
func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	a, b := make(chan int), make(chan int)
	go func() {									//接收端
		defer wg.Done()			
		for {
			var (
				name string
				x    int
				ok   bool
			)
			select {						//随机选择可用channel接收数据
			case x, ok = <-a:
				name = "a"
			case x, ok = <-b:
				name = "b"
			}
			if !ok {						//如果任意通道关闭,则终止接收
				return
			}
			println(name, x)				//输出接收的数据信息
		}
	}()
	go func() {								//发送端
		defer wg.Done()
		defer close(a)
		defer close(b)
		for i := 0; i < 10; i++ {
			select {						//随机选择发送channel
			case a <- i:
			case b <- i * 10:
			}
		}
	}()
	wg.Wait()
}
//b 0
//b 10
//b 20
//b 30
//a 4
//b 50
//a 6
//b 70
//b 80
//b 90
posted @   李光宇  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示