• Go语言的并发


    使用协程这种并发模式是趋势,协程的基本要求是:并发执行和可大量创建。

    一些语言已经支持协程,下面这个图来自:http://qing.weibo.com/tj/88ca09aa33002ele.html

    NewImage

    这种并发模式的内核只需要协程和通道就够了。其中协程负责执行代码,通道负责在协程之间传递事件。

    NewImage

            协程是轻量级的线程。在过程式编程中,当调用一个过程的时候,需要等待其执行完才返回。而调用一个协程的时候,不需要等待其执行完,会立即返回。协程十分轻量,Go语言可以在一个进程中执行有数以十万计的协程,依旧保持高性能。而对于普通的平台,一个进程有数千个线程,其CPU会忙于上下文切换,性能急剧下降。随意创建线程可不是一个好主意,但是我们可以大量使用的协程。

            通道是协程之间的数据传输通道。通道可以在众多的协程之间传递数据,数据具体可以值也可以是个引用。通道有两种使用方式。

    • 协程可以试图向通道放入数据,如果通道满了,会挂起协程,直到通道可以为他放入数据为止。
    • 协程可以试图向通道索取数据,如果通道没有数据,会挂起协程,直到通道返回数据为止。

            如此,通道就可以在传递数据的同时,控制协程的运行。有点像事件驱动,也有点像阻塞队列。

    Go语言创建协程很简单,只需要简单的在函数前用关键字 go即可。

    https://github.com/astaxie/build-web-application-with-golang/blob/master/ebook/02.7.md 

    下面的代码例子是通过2分法,开两个协程分别计算和,然后再合并这两个计算和。

    注意,这里的 通道 写入和读取都是阻塞的, 这样就可以保证 两个汇总都计算完了,才会执行 fmt.Println(x, y, x+y)

    package main

     

    import "fmt"

     

    func sum(a []int, c chan int) {

        sum := 0

        for _, v := range a {

            sum += v

        }

        c <- sum // send sum to c

    }

     

    func main() {

        a := []int{7, 2, 8, -9, 4, 0}

     

        c := make(chan int)

        go sum(a[:len(a)/2], c)

        go sum(a[len(a)/2:], c)

        x, y :=<-c, <-c // receive from c

     

        fmt.Println(x, y, x+y)

    }

    默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。

    所谓阻塞,也就是如果读取不到 (value := <-ch)它将会被阻塞,直到有数据接收。

    另外,如果ch中有数据,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。

    Buffered Channels

    Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。 

    ch := make(chan type, value) 
    value == 0 ! 无缓冲(阻塞)
    value > 0 ! 缓冲(非阻塞,直到value个元素满了才会阻塞)

    比如下面的代码会报错误: 

    package main

     

    import "fmt"

     

    func main() {

        c :=make(chanint, 1) //1报错,修改2为3可以正常运行

        c <- 1

        c <- 2

        fmt.Println(<-c)

        fmt.Println(<-c)

    }

    错误信息:

    throw: all goroutines are asleep - deadlock!

      

      goroutine 1 [chan send]:

      main.main()

        /Users/cybercare/go/src/test1/main.go:8 +0x70

      

      goroutine 2 [syscall]:

      created by runtime.main

        /usr/local/go/src/pkg/runtime/proc.c:221


    Range和Close 

    Channel 也可以用 Range 进行遍历。

    下面的例子是利用协程计算 斐波那契數列,每次计算出来的值都通过通道打印出来。直到调用close关闭通道。

    package main

     

    import (

        "fmt"

    )

     

    func fibonacci(n int, c chan int) {

        x, y := 1, 1

        for i := 0; i < n; i++ {

            c <- x

            x, y = y, x+y

        }

        close(c)

    }

     

    func main() {

        c := make(chan int, 10)

        go fibonacci(cap(c), c)

        for i := range c {

            fmt.Println(i)

        }

    }

    这段代码执行的结果如下:除了前2个数,其他数都是前两个数相加之和。

    1
    1
    2
    3
    5
    8
    13
    21
    34
    55

    生产者通过关键字close函数关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法v, ok := <-ch测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。

    记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它

    Select

    上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们可以通过select可以监听channel上的数据流动。

    select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。 

    在select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。

    http://www.sharejs.com/codes/go/4415

    下面代码是斐波那契數列的一个调整,执行结果不确定,这是因为select是随机的选择一个执行的。这里有default函数,default函数也会随机被执行到。如果没有default函数,这个执行结果是固定的。

    package main

     

    import "fmt"

     

    func fibonacci(c, quit chan int) {

        x, y := 1, 1

        for {

            select {

            case c <- x:

                x, y = y, x+y

            case <-quit:

                fmt.Println("quit")

                return

            default:

                fmt.Println("default")

            }

        }

    }

     

    func main() {

        c := make(chan int)

        quit := make(chan int)

        go func() {

            for i := 0; i < 10; i++ {

                fmt.Println(<-c)

            }

            quit <- 0

        }()

        fibonacci(c, quit)

    }

    超时

    select 语句使得一个 goroutine 在多个通讯操作上等待。
    select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。

    对 select 的 case ,它只能是  receive, send , assign recv 三者之一。

    http://golang.org/pkg/time/#After

    https://code.google.com/p/go-wiki/wiki/Timeouts

    package main

     

    import (

        "fmt"

        "time"

    )

     

    func main() {

        c := make(chan int)

        o := make(chan bool)

        go func() {

            for {

                select {

                case v := <-c:

                    fmt.Println(v)

                case <-time.After(5 * time.Second):

                    fmt.Println("timeout 5s")

                    o <- true

                    break

                }

            }

        }()

        <-o

    }

    这里信道 o 的目的就是确保 协程 一直被执行。 我们如果从信道 c 中读取数据超时 5秒的话,就会触发 <-time.After(5 * time.Second), 继而给信道 o 中放入一个数据,从而应用关闭。

    真实的超时代码应该是类似下面方式的伪代码:

    import "time"

    c
    := make(chan os.Error,1)
    go func
    (){ c <- client.Call("Service.Method", args,&reply)}()
    select{
     
    case err :=<-c:
       
    // use err and reply
     
    case<-time.After(timeoutNanoseconds):
       
    // call timed out
    }

    参考资料:

    Go语言_并发篇
    http://www.cnblogs.com/yjf512/archive/2012/06/06/2537712.html

    2.7 并发
    https://github.com/astaxie/build-web-application-with-golang/blob/master/02.7.md

    Go-简洁的并发
    http://www.yankay.com/go-clear-concurreny/

    Go语言并发之美
    http://qing.weibo.com/tj/88ca09aa33002ele.html

    Go的并发模式:超时、继续
    http://floss.zoomquiet.org/data/20120427161151/index.html

    go 语言并发机制 goroutine 初探
    http://xiezhenye.com/2011/11/go-%E8%AF%AD%E8%A8%80%E5%B9%B6%E5%8F%91%E6%9C%BA%E5%88%B6-goroutine-%E5%88%9D%E6%8E%A2.html

    Go语言并发
    http://www.yiibai.com/go/go_Complicating.html

  • 相关阅读:
    一个网站需求说明书的示例
    产品设计与PRD介绍
    研发效能度量案例
    项目管理过程流程图
    变量 $cfg['TempDir'] (./tmp/)无法访问。phpMyAdmin无法缓存模板文件,所以会运行缓慢。
    wordpress函数大全列表整理
    PCLZIP_ERR_BAD_FORMAT (-10) : Unable to find End of Central Dir Record signature
    通过写脚本的方式自动获取JVM内的进程堆栈信息等内容
    简单定位占用最高CPU的java进程信息
    使用linux上面powershell安装vm powercli 连接vcenter 通过计划任务自动创建部分虚拟机的快照以及自动清理过期快照的办法
  • 原文地址:https://www.cnblogs.com/ghj1976/p/2976025.html
Copyright © 2020-2023  润新知