赞
踩
协程(coroutine)是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。
在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用的函数返回时,这个goroutine也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
- func longWait() {
- fmt.Println("Beginning longWait()")
- time.Sleep(5 * 1e9)
- fmt.Println("End of longWait()")
- }
-
- func shortWait() {
- fmt.Println("Beginning shortWait()")
- time.Sleep(2 * 1e9)
- fmt.Println("End of shortWait()")
- }
-
- func main(){
- fmt.Println("main begin")
- time.Sleep(3 * 1e9)
-
- go shortWait()
- go longWait()
- fmt.Println("main end")
- }
执行上述代码,发现longWait没有执行到,shortWait全部执行完毕,这时候main函数结束就不会在执行longWait。如果我们想要让main()函数等待所有goroutine退出后再返回,但如何知道goroutine都退出了呢?这就引出了多个goroutine之间通信的问题。
channel是引用类型,是CSP格式的个体实现,用于多个goroutine通讯,其内部实现了同步,确保并发安全。
当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此,调用者或被调用者将引用一个channel对象,和其它的类型一样,channel的零值也是nil。
- chan定义
- make(chan type) //等坐于 make(chan type,0)
- make(chan type,capacity) //capacity是容量
- //当capacity=0时,channel是无缓冲阻塞读写的,当capacity>0时,channel是有缓冲非阻塞的,直到写满capacity元素才阻塞写入。
-
- //channel通过操作符<-来接收和发数据
- ch := make(chan int) //创建一个int类型管道
- ch <- value //发送value到ch
- <-ch //接收并且丢弃
- num := <-ch //从ch中接收数据,并赋值给num
- num, ok := <-ch //功能上和上面的一样,但是它还同时检查是否已经关闭或者为空
-
- var ch1 chan int // 普通channel
- var ch2 chan <- int // 只用于写int数据
- var ch3 <-chan int // 只用于读int数据
channel作为一种原生类型,本身也可以通过channel进行传递,例如下面这个流式处理结构:
- type PipeData struct {
- value int
- handler func(int) int
- next chan int
- }
-
- func handle(queue chan *PipeData) {
- for data := range queue {
- data.next <- data.handler(data.value)
- }
- }
下面是用chan来解决并发的问题,chan相当于队列,先入先出,当有数据进入chan中后,num才能获取chan中的数据,主线程才能继续执行。如果没有数据写入chan,就会一直阻塞。
select()函数用来监控一组描述符,该机制常被用于实现高并发的socket服务器程序。Go语言直接在语言级别支持select关键字,用于处理异步IO问题,大致结构如下:
1.select+case是用于阻塞监听goroutine的,如果没有case,就单单一个select{},则为监听当前程序中的goroutine,此时注意,需要有真实的goroutine在跑,否则select{}会报panic。
2.select底下有多个可执行的case,则随机执行一个。
3.select常配合for循环来监听channel有没有故事发生。需要注意的是在这个场景下,break只是退出当前select而不会退出for,需要用break TIP / goto的方式。
4.无缓冲的通道,则传值后立马close,则会在close之前阻塞,有缓冲的通道则即使close了也会继续让接收后面的值。
- func main() {
- ch := make(chan int,1)
- for i := 0; i < 10; i++{
- select {
- case ch <- i:
- case x := <- ch:
- fmt.Println(x)
- }
- }
- }
如果管道没有指定大小就会产生一个死锁,运行后代码输出0,2,4,6,8。因为这个管道的缓冲值只有1
,那么同一时间只会有一个case
执行,这个channel
不是空的就是满的。
select的case语句中,都是对应一个I/O操作,准确的说是对应一个channel的I/O操作,那么到这里也应该可以理解为什么在code-1中,一个无缓冲的channel能在那段代码中产生一个deadlock
。当某个case得到执行后,就会退出select。
go中协程也会存在一些资源竞争,比如下面代码如果不加锁的话运行结果很难得到5000,加锁后可以得到想要的结果。
- var lock sync.Mutex
-
- type Money struct {
- amount int
- }
-
- func (m *Money)Add(i int) {
- m.amount += i
- }
- func (m *Money) Minute(i int) {
- lock.Lock()
- defer lock.Unlock()
- if m.amount >= i {
- m.amount = m.amount - i
- }
- }
- func (m *Money) Get() int {
- return m.amount
- }
-
- func main() {
- m := new(Money)
- m.Add(10000)
- for i := 0; i < 1000; i++ {
- go func() {
- time.Sleep(500 * time.Millisecond)
- m.Minute(5)
- }()
- }
-
- time.Sleep(2 * time.Second)
- fmt.Println(m.Get())
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。