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
//在进行多变量赋值操作时,首先计算出所有的右值,然后在依次完成赋值操作
- 在常量组中如不指定类型和初始值,则与上一行非常量右值()表达式文本相同
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
-
常量不能取值,而是被作为指令数据展开
-
标准库maht定义了各数字类型的取值范围
-
标准库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
-
浮点数需注意小数位的有效精度
-
别名 byte is uint8, rune is int32
-
引用函数必须使用make()函数来创建,引用类型包括(map ,slice , channel)
-
无显示的常量在参与运算时会自动转换类型
-
自增,自减不再是运算符,只能作为独立语句,不能用于表达式,
-
指针类型支持相等运算符,但不能做加减法运算和类型转换,如果指针指向同一地址,或都为nil,那么他们相等
-
switch 支持语句初始化
package main
func main(){
switch x := 5;x {
default : //编辑器确保不会先执行default
x += 100
println(x)
case 5:
x += 50
println(x)
}
}
//55
-
switch 隐式brack,如需贯通,可使用fallthrough
-
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
-
在for循环中,初始化语句仅执行一次,
-
函数只能判断是否为nil,不支持其他比较操作
-
变参本质上就是一个切片,只能接收一到多个同类型参数,且必须放在列表尾部
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]
- 有返回值的函数,必须有明确的return终止语句
- 闭包
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
- 延迟调用 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
- 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
- 字符串默认值不是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]
}
- 数组
- 对于结构等复合类型,可省略元素初始化类型标签
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]
}
- 切片
- 切片本身并非动态数组或数组指针,它内部通过指针引用底层数组,设定相关属性将数据读写操作限定在指定区域内
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
}
- 字典(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 实现同步,避免读写操作同时进行
- 结构
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}
}
- 方法
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)如方法内部并不引用实例,可省略参数名,仅保留类型
-
不能用多级指针调用方法
- 接口
- 接口命名方式 以 er为后缀结尾
- 并发和并行的区别
-
并发:逻辑上具备同时处理多个任务的能力
-
并行:物理上在同一时刻执行多个并发任务
- 与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
- 通道
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构