zoukankan      html  css  js  c++  java
  • 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 // 阻塞,等待程序结束
    }
  • 相关阅读:
    回家了
    AMP > Chapter 3 Concurrent Objects > Notes<1>
    Readings in Database Systems
    读书笔记:《Transaction Processing》Chapter 13 Buffer Management

    委托和事件的区别
    .net网页不完整的解决方案
    聚集索引,非聚集索引
    固定宽度下拉列表中option内容显示不全问题解决方法
    让你的Windows系统自动释放系统资源
  • 原文地址:https://www.cnblogs.com/yangshixiong/p/12134650.html
Copyright © 2011-2022 走看看