go入门7 -- goroutine

终于来到了go最强悍的地方语言层面支持并发,这是一种类似于协程的机制, gorutine是一个非常轻量级的实现,一般情况下,单进程可以实现千万级的并发任务。先来搞一下基本概念:

1.注意并发跟并行的区别:

  • 并发性(concurrency),又称共行性,是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。
  • 并行(parallelism)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行

举个形象的例子就是并发就是拉链式车道,就是三车道在某个路口合并到单车道,那个车道有车,就依次并入单车道,而并行就是三车道,想要行驶,您随意,毕竟是人民币玩家。

想要更牛的效率,那就需要高配,推荐苹果垃圾桶,28核,30+w软妹币,但一般而言,我们遇到大多情况都是io操作,没有那么多计算,所以go的并发真真正正实现低配电脑也能高效,这也就是go的魅力所在吧,一般情况下,go默认的进程启动后一个系统线程服务于goroutine,但是如果是人民币玩家,可以使用 runtime.GOMAXPROCS 修改,让调度器用多个线程实现多核并行。

2.goroutine之间的内存不是共享的,但为了实现goroutine之间的通信,可以使用channel,实现“以通讯来共享内存”

3.gorutine的执行顺序不是一定的,并且当前进程推出时,不会等你起的那些gorutine结束,

这个就是简单起了10个gorutine,终端打印结果是不一定的,跟电脑状态有关,简单列了三次打印情况,我这个电脑比较垃圾,是MacAir,攒钱换垃圾桶

func tell(i int){
    fmt.Println("i am ", i)
}

func main(){
    fmt.Println("start")
    for i := 0; i < 10; i++{
        go tell(i)
    }
    fmt.Println("end")
}
1:
start
i am  1
i am  0
end
i am  2
2:
start
end
i am  5
i am  9
3:
start
end

这时就有一个疑问,我们想要所有的gorutine执行完毕,在结束程序怎么做,这里有几种方法

可以使用sync包中的WaitGroup,它能够一直等所有的groutine执行完毕,在此之前会一直阻塞主进程,ADD是告知增加了几个gorutine,Done是告知一个gorutine完结,相当于Add(-1),

Wait会一直阻塞,直到这里面的值为空

func tell(i int) {
    fmt.Println("i am ", i)
}

func main() {
    var wg sync.WaitGroup
    fmt.Println("start")

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            tell(i)
        }(i)
    }
    wg.Wait()
    fmt.Println("end")
}
//start
//i am  4
//i am  5
//i am  9
//i am  7
//i am  8
//i am  6
//i am  2
//i am  1
//i am  0
//i am  3
//end

另外对于终结当前的gorutine,可以使用runtime.Goexit(),这个会直接推出当前的gorutine,调度器会确保所有已经注册的defer会执行,其余的不执行

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        defer println("A.defer") // 执行
        func() {
            defer println("B.defer") // 执行
            runtime.Goexit()
            defer println("B.after defer")
            println("B")
        }()
        println("A")
    }()
    wg.Wait()
}

goroutine有太多作用啦,比如你要送发消息,邮件,短信,等等,可以直接起个gorutine,然后返回,高效。

下面来讲讲与之配套的channel

channnel默认是同步模式,需要发送与接收配对,否则就会被阻塞,直到另一方准备好,就是消费者生产者模型,默认开启的chan缓存区就一个,因此在往里面存放数据的时候,如果没有取走,会阻塞住,因此close(msg)会等待存完,取完后在执行,因此可以这样理解,缓冲区已满,则发送会被阻塞,如果缓存区为空,则接受会被阻塞

func main() {
msg := make(chan int)
flag := make(chan bool)
go func(){
fmt.Println("开始接受啦")
for i := range msg{
fmt.Println(i)
}
flag <- true
}()

for i := 0; i < 10; i++{
msg <- i
}
close(msg)
fmt.Println("关闭msg通道")
fmt.Println(<-flag)
}
}
//

开始接受啦
0
1
2
3
4
5
6
7
8
9
关闭msg通道
true

比如,我开启的channel缓存区比较大,那么存放数据不会被阻塞,就会直接执行,那为什么主程序没有直接关闭呢,那是因为flag这个channel阻塞了,他会一直等到gorutine往里面存入数据,才可以取出来,如果没有flag这个channel,程序会直接推出,也就看不到起的gorutine打印结果啦。

func main() {
    msg := make(chan int, 10)
    flag := make(chan bool)
    go func() {
        fmt.Println("开始接受啦")
        for i := range msg {
            fmt.Println(i)
        }
        flag <- true
    }()

    for i := 0; i < 10; i++ {
        msg <- i
    }
    close(msg)
    fmt.Println("关闭msg通道")
    fmt.Println(<-flag)
}
//
关闭msg通道
开始接受啦
0
1
2
3
4
5
6
7
8
9
true

如果向已关闭的channel发送数据,则会报错,陪panic捕捉,如果channel关闭两次,也会报错

func main() {
    msg1 := make(chan int)
    close(msg1)
    //close(msg1)  // 关闭两次,panic: close of closed channel
    fmt.Println(<-msg1) // 已关闭取出的值是空值,int空值为 0
    //msg1 <- 2           // panic: send on closed channel
    i, ok := <-msg1 // 可以检查channel是否已关闭
    if ok {
        fmt.Println(i)
    } else {
        fmt.Println("msg1 closed") // msg1 closed
    }
}

单向channel,可以将channel设置为只接受,或者只发送,send为只能发送的,如果从里面取数据,就会报错这个需要指定缓冲区,否则会报错,反之亦然,例如

func main() {
     m := make(chan int, 1)
    var send chan <- int = m
    var recv <-chan int =m
    send <- 1
    //<- send  // invalid operation: <-send (receive from send-only type chan<- int)
    //recv <- 1  // invalid operation: recv <- 1 (send to receive-only type <-chan int)
    println(<- recv) // 1
}

 channel结合select也可以设置超时

func main() {
    w := make(chan bool)
    c := make(chan int, 2)
    go func() {
        select { // 如果两个case都不满足,会一直等到满足
        case v := <-c:
            fmt.Println(v)
        case <-time.After(time.Second * 3):
            fmt.Println("timeout.")
        }
        w <- true
    }()
    <-w // 阻塞,等待程序结束
}

猜你喜欢

转载自www.cnblogs.com/yangshixiong/p/12134650.html