这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
协程goroutine
与 传统的系统级线程和进程相比,协程的最大优势在于其“轻量级”,可以轻松创建上百万个而不 会导致系统资源衰竭,而线程和进程通常最多也不能超过1万个。这也是协程也叫轻量级线程的原因。
协程又称为"轻量级线程". 协程的内存一般为kb量级, 比mb量级的线程占用空间小很多. goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。
Go语言的协程支持非常好, 仅仅需要go关键字即可启动一个goroutine.
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
func main() {
for i := 0; i < 10; i++ {
go Add(i, i)
}
}
但是如果运行这段代码, 控制台不会输出任何东西, 原因在于main函数进行完for循环之后就直接退出了, 没有留给十个goroutine执行的机会. 在实践中我也会常常苦恼, 为啥这段并发输出跟我想象的不一样.
看来人脑对并发的支持不够好.
channel
channel是goroutine之间通信的最好方式. 在很多传统语言利用共享内存来实现并发, 而Go的口号是: 不要通过共享内存来通信,而应该通过通信来共享内存
如果用共享内存(此处共享了一个变量) + 加锁的方式实现并发将会非常麻烦.
Go中用更优雅的方式, 即channel实现并发通信
func Count(ch chan int) {
ch <- 1
fmt.Println("Counting")
}
func main() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i])
}
for _, ch := range chs {
<-ch
}
}
channel的使用
- 声明chan:
var chanName chan ElementType
var m map[string] chan bool
- 初始化: (如果在容器中则需要遍历)
ch := make(chan int, 1) //后面不跟数字代表没有缓冲区
- 写入与读出: 向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据
ch <- value
value := <-ch
- 单向channel:
var ch1 chan int// ch1是一个正常的channel,不是单向的
var ch2 chan<- float64// ch2是单向channel,只用于写float64数据
var ch3 <-chan int// ch3是单向channel,只用于读取int数据
select
每一个case都必须包含一个IO操作, 所以select可以用于处理异步IO问题.
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}