Go语言入门总结(八)-并发编程

洋葱er · · 390 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1.goroutine

在go语言里面实现并发很容易。
独立运行的任务被称为goroutine,goroutine不同于线程,它的运行表面上看似乎都是在同时运行,但是由于计算机通常只具有有限数量的处理单元,所以,从技术上来说,goroutine并不是真正的同时在运行。其实相当于是在一条时间轴上,go语言会分配时间,让他们轮流运行。至于运行顺序,那将是随机的。
启动goroutine就像调用函数一样简单,只需要在调用的前面加上go就可以了。

func main() {
    for i := 0;i <= 5;i++{
        go printNum(i)
    }
    time.Sleep(1 * time.Second)
}

func printNum(i int){
    fmt.Println(strconv.Itoa(i))
}

上面这段代码在每次运行时,你都可以看到不同的运行结果,这也证实了运行顺序的随机性。
但是一旦注释掉time.Sleep(1 * time.Second)这句代码,你就得不到任何输出。
因为golang的主函数(其实也是跑在一个goroutine中)并不会等待其他goroutine结束。如果主goroutine结束了,所有其他goroutine都将结束。

2.channel(通道)

和go语言的其他类型一样,你可以将通道用作变量、传递至函数、存储在结构中,或者其它你想做的事情。
通道是负责在多个goroutine之间进行通信的。
创建一个通道使用make函数创建 ,如 c := make(chan int),chan表示是通道类型,int表示这个通道的接受的值类型。
数据使用 <- 来进行传递,放在chan变量的左边表示把chan接收到的值传递给一个变量,放在右边表示将数据传递给chan。

c <- 99  //表示将99传递给chan变量c
v := <- c //表示将chan变量c里面的值赋值给变量v

当在执行发送操作,也就是正在执行将值传递给chan变量的操作时,执行该操作的goroutine任务会处于等待的过程中,无法执行其它操作。但是其它未等待的扔可以继续自由的运行。执行接收操作的也一样,接收的会等待接收到下一个数据以后才会继续执行。

func main() {
    c := make(chan int)
    for i := 0;i <= 5;i++{
        go printNum(i,c)//1.for循环先会创建6个goroutine任务,去执行,对于goroutine的执行是无序的。
    }
    time.Sleep(200) //4.增加sleep是为了让读者更直观的感受到chan等待时后续代码是不执行的。因为等待,所以第3步发送给chan的数据没有接收者。
    for i := 0;i <= 5;i++{
        fmt.Println("get " + strconv.Itoa(i))//5.等待结束,开始执行打印
        newi := <- c  //6.接收到了通道传来的值。但是接收到的6个数据肯定是按通道发送数据的顺序接收到的。但是下面一条语句执行的时机就不一定了
        fmt.Println("get end " + strconv.Itoa(newi))7.这个执行肯定是要在第6步执行完成以后,再根据系统分配,随机执行了。但是执行的肯定都是已经接收到值了的。
    }
    time.Sleep(1 * time.Second)
}

func printNum(i int,c chan int){
    fmt.Println("go " + strconv.Itoa(i)) //2.先执行到这里,创建出的6个goroutine会先执行这一句,即使下面一句代码的通道处于等待状态也不影响6个goroutine这句的执行。
    c <- i //3.当第一个goroutine执行到这里的时候发现没有通道接收值的时候,就会在这里等待。后面5个goroutine会在这里排队,后面5个排队执行的顺序也要看系统分配。
    fmt.Println("go end " + strconv.Itoa(i))7.这个执行肯定是要在第3步执行完成以后,再根据系统分配,随机执行了。但是执行的肯定都是已经发送过值了的。
}

上面这段代码读者运行一下就可以很清楚channel的等待机制。
具体分析可以看上面代码的注释部分。

3.select处理通道

上面介绍channel的时候使用的都是int类型的chan,所以这其实是一种理想情况,真正的使用时,可能会存在不同类型的chan。这个时候我们就可以使用select来处理不同类型的chan了。

func main() {
    c := make(chan int)
    for i := 0;i <= 5;i++{
        go printNum(i,c)
    }
    timeOut := time.After(time.Second)
    for i := 0;i <= 5;i++{
        select {
        case newi := <- c:
            fmt.Println(strconv.Itoa(newi))
        case <- timeOut:
            fmt.Println("运行超时")
            return
        }

    }
}

func printNum(i int,c chan int){
    time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
    c <- i
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:洋葱er

查看原文:Go语言入门总结(八)-并发编程

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

390 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传