协程问题
- 通过全局变量加锁同步来实现通讯,并不利于多个协程对全局变量的读写操作。
- 加锁虽然可以解决goroutine对全局变量的抢占资源问题,但是影响性能,违背了原则。
- 解决:使用channel进行协程goroutine间的通信。
channel
- Go语言的并发模型是CSP(Communicating Sequential Processes)
- 提倡通过通信共享内存而不是通过共享内存而实现通信,因此引出了channel。
- 线程与协程的关系
- 一个操作系统线程对应用户态多个goroutine。
- go程序可以同时使用多个操作系统线程。
- goroutine和OS线程是多对多的关系,即m:n。
- for range 走ch1,ch2
- for range 从通道中取值,通道关闭时for range 退出
-
ch1:=make(chan int) ch2:=make(chan int) //goroutine开启 go func(){ for i:=0;i<100;i++{ ch1<-i } close(ch1) }() //goroutine开启,从ch1取值 go func(){ for{ i,ok:=<-ch1//通道取值后,平方走 if ok{ ch2<-i*i }else{ break } ch1<-i*i } close(ch2) }() for i:=range ch2{ fmt.Println(i) }
- 单通道,只读和只写
- defer 用于通道关闭
- in chan<- int:写
- out <-chan int: 读
-
func counter(in chan<- int) { defer close(in) for i := 0; i < 100; i++ { in <- i } } func square(in chan<- int, out <-chan int) { defer close(in) for i := range out { in <- i * i } } func output(out <-chan int) { for i:=range out{ fmt.Println(i) } } func main() { ch1 := make(chan int) ch2 := make(chan int) go counter(ch1) go square(ch2, ch1) output(ch2) }
-
work pool 防止goroutine暴涨和泄漏
-
func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { //开始处理事务id与任务job fmt.Printf("worker:%d start job:%d\n", id, j) //设定合适的时间 time.Sleep(time.Second) //结束处理事务id与任务job fmt.Printf("worker:%d end job:%d\n", id, j) //传入chan:results中 results <- j * 2 } } func main() { jobs := make(chan int, 100) results := make(chan int, 100) // 先开启3个goroutine(w=1,2,3) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } // 为chan jobs传入j的5个任务(1,2,3,4,5) for j := 1; j <= 5; j++ { //传入后,worker可以进行处理,results通道接收到值 jobs <- j } //传完结束 close(jobs) //继续传 for a := 1; a <= 5; a++ { <-results } }
-
- select、case
- select:(使用select语句能提高代码的可读性)
- case:(如果多个case同时满足,select会随机选择一个。)
- 对于没有case的select{}会一直等待,可用于阻塞main函数。
-
//可处理一个或多个channel的发送/接收操作。 // ch := make(chan int, 1) go func() { for i := 0; i < 10; i++ { select { case x := <-ch: fmt.Println(x) case ch <- i: } } }()
查漏补缺
-
打印变量类型
fmt.Printf函数格式中的%T动词(优先,效率高)- 使用反射包
reflect中的TypeOf函数
-
switch更好用
- case后支持多个参数 case 1,2,3,4:
- case后加判断 case a>10:
-
fallthrough
- switch:每个 case 无需声明 break 来终止
- 想顺序执行使用 fallthrough
- case 10:下面的语句最后加fallthrough,就可以像c++switch一样往下继续遍历
-
array和slice
-
array
- 固定长度,元素类型两个字段
var a = [5]int{1, 2, 3, 4, 5}
-
slice
- 不固定长度,字段:有指向一个数组的指针,len和cap共三个
var nums = []int{1, 2, 3, 4, 5, 6}
-
初始化
- 直接声明 slice 的方式内部是不申请内存空间的,slice 内部 array 指针指向 null。
- 使用 make 关键字会申请包含 0 个元素的内存空间,底层 array 指针指向申请的内存。
- 序列化
- json.Marshal(直接声明): 返回 null
- json.Marshal(make关键字初始化): 返回 []
-
总结
-
很多要学的东西,继续加油咯