前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang学习笔记之并发.协程(Goroutine)、信道(Channel)

Golang学习笔记之并发.协程(Goroutine)、信道(Channel)

作者头像
李海彬
发布2018-12-27 14:51:02
1.3K0
发布2018-12-27 14:51:02
举报
文章被收录于专栏:Golang语言社区Golang语言社区

原文作者:学生黄哲 来源:简书

Go是并发语言,而不是并行语言。
一、并发和并行的区别

•并发(concurrency)是指一次处理大量事情的能力。并发的关键是你有处理多个任务的能力,不一定要同时。 •并行(parallelism)指的是同时处理多个事情。并行的关键是你有同时处理多个任务的能力。

简单的理解一下,并发就是你在跑步的时候鞋带开了,你停下来系鞋带。而并行则是,你一边听歌一边跑步。 并行并不代表比并发快,举一个例子,当文件下载完成时,应该使用弹出窗口来通知用户。而这种通信发生在负责下载的组件和负责渲染用户界面的组件之间。在并发系统中,这种通信的开销很低。而如果这两个组件并行地运行在 CPU 的不同核上,这种通信的开销却很大。因此并行程序并不一定会执行得更快。 Go 原生支持并发。在Go中,使用 Go 协程(Goroutine)和信道(channel)来处理并发。

二、Go协程(Goroutine)

只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员⽆需了解任何执⾏细节,调度器会⾃动将其安排到合适的系统线程上执⾏。协程是⼀种⾮常轻量级的实现,可在单个进程⾥执⾏成千上万的并发任务。

•调度器不能保证多个 goroutine 执⾏次序,且进程退出时不会等待它们结束。 •Go 协程之间通过信道(channel)进行通信。 •协程里可以创建协程

(1)协程的创建
代码语言:javascript
复制
 1package main
 2import (
 3    "fmt"
 4    "time"
 5)
 6func hello() {
 7    fmt.Println("Hello world goroutine")
 8}
 9func main() {
10    //开启了一个新的协程。hello() 函数将和 main() 函数一起运行。
11    go hello()
12    time.Sleep(1 * time.Second) //延时结束主程序,不然不能保证主程序会等协程程序
13    fmt.Println("main function")
14}
(2)创建多个协程
代码语言:javascript
复制
 1package main
 2import (
 3    "fmt"
 4    "time"
 5)
 6func numbers() {
 7    for i := 1; i <= 5; i++ {
 8        time.Sleep(250 * time.Millisecond)
 9        fmt.Printf("%d ", i)
10    }
11}
12func alphabets() {
13    for i := 'a'; i <= 'e'; i++ {
14        time.Sleep(400 * time.Millisecond)
15        fmt.Printf("%c ", i)
16    }
17}
18func main() {
19    //numbers()和alphabets()并发执行
20    go numbers()
21    go alphabets()
22    time.Sleep(3000 * time.Millisecond)
23    fmt.Println("Main Over")
24}

输出: 1 a 2 3 b 4 c 5 d e Main Over

(3)调⽤ runtime.Goexit()将⽴即终⽌当前 goroutine 执⾏。但所有已注册 defer延迟调⽤会被被执⾏。

修改一下上面的代码

代码语言:javascript
复制
1func alphabets() {
2    defer fmt.Println("结束") //defer会被调用
3    for i := 'a'; i <= 'e'; i++ {
4        time.Sleep(400 * time.Millisecond)
5        fmt.Printf("%c ", i)
6        runtime.Goexit() //立即结束该协程
7    }
8}

程序输出则会变为 1 a 结束 2 3 4 5 Main Over

(4)调⽤ runtime.Gosched()将当前 goroutine 暂停,放回队列等待下次被调度执⾏。
代码语言:javascript
复制
 1package main
 2import (
 3    "fmt"
 4    "runtime"
 5)
 6func main() {
 7    go func() { //子协程   //没来的及执行主进程结束
 8        for i := 0; i < 5; i++ {
 9            fmt.Println(i)
10        }
11    }()
12
13    for i := 0; i < 2; i++ { //默认先执行主进程主进程执行完毕
14        //让出时间片,先让别的协议执行,它执行完,再回来执行此协程
15        runtime.Gosched()
16        fmt.Println("执行")
17    }
18}
三、信道(Channel)

信道(Channel)可以被认为是协程之间通信的管道。数据可以从信道的一端发送并在另一端接收。

•默认为同步模式,需要发送和接收配对。否则会被阻塞,直到另⼀⽅准备好后被唤醒。 •信道支持单向信道

信道分为无缓冲信道和有缓冲信道 无缓冲信道:信道是同步的,接收前没有能力保存任何值。这种类型的信道只有发送和接收同时准备好,才能进行下次信道的操作,否则会导致阻塞。 有缓冲信道:信道是异步的,是一种在被创建时就被开辟了能存储一个或者多个值的信道。这种类型并不要求发送与接收同时进行。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。

信道声明 var ch chan T 我们声明了一个T类型的名称叫做ch的信道

信道的 0 值为 nil。我们需要通过内置函数 make 来创建一个信道,就像创建 map 和 slice 一样。

无缓冲信道 ch := make(chan T) 有缓冲信道 ch := make(chan T,cap)

(1)信道的创建
代码语言:javascript
复制
 1//内置类型channel
 2    var a chan int
 3    if a == nil {
 4        a = make(chan int)
 5        fmt.Printf("%T\n", a) //chan int
 6    }
 7    //自定义类型channel
 8    var p chan person
 9    if p == nil {
10        p = make(chan person) //chan main.person
11        fmt.Printf("%T\n", p)
12    }
(2)通过信道发送和接收数据
代码语言:javascript
复制
1data := <- a // 从信道 a 中读取数据并将读取的值赋值给变量 data 。
2a <- data // 向信道 a 中写入数据。
(3)发送和接收默认是阻塞的
代码语言:javascript
复制
 1package main
 2import (
 3    "fmt"
 4    "time"
 5)
 6func hello(done chan bool) {
 7    fmt.Println("hello go routine is going to sleep")
 8    time.Sleep(4 * time.Second)
 9    //只有写数据后才能继续执行
10    done <- true
11    fmt.Println("hello go routine awake and going to write to done")
12}
13func main() {
14    done := make(chan bool)
15    go hello(done)
16    <-done
17    fmt.Println("Main received data")
18}
(4)死锁

使用信道是要考虑的一个重要因素是死锁(Deadlock)只读未写与只写未读都会触发死锁,并触发 panic 。

channel 上如果发生了流入和流出不配对,就可能会发生死锁。

代码语言:javascript
复制
1package main
2func main() {
3    ch := make(chan int)
4    ch <- 5    //只写未读触发死锁
5}
(6)单向信道与关闭信道close()

发送者可以关闭信道以通知接收者将不会再发送数据给信道。 v, ok := <- ch判断信道是否已关闭

代码语言:javascript
复制
 1package main
 2import (
 3    "fmt"
 4)
 5//只写操作
 6func sendData(sendch chan<- int) {
 7    sendch <- 10
 8
 9    //不能读
10    //<-sendch
11
12    close(sendch) //显式关闭信道
13}
14//只读操作
15func readData(sendch <-chan int) {
16    <-sendch
17}
18func main() {
19    sendch := make(chan int)
20    go sendData(sendch)
21    v, ok := <-sendch //ok 返回 true 表示成功的接收到了发送的数据,如果 ok 返回 false 则表示信道已经被关闭。
22    v1, ok1 := <-sendch
23    fmt.Println(v, ok)   //10 true
24    fmt.Println(v1, ok1) //0 false
25}
(7)遍历信道

信道支持range for遍历

代码语言:javascript
复制
 1package main
 2
 3import (
 4    "fmt"
 5    "time"
 6)
 7
 8func producer(chnl chan int) {
 9    defer close(chnl) //程序执行结束关闭信道
10    for i := 0; i < 10; i++ {
11        time.Sleep(300 * time.Millisecond) //一秒写一次
12        chnl <- i                          //写操作
13    }
14}
15func main() {
16    ch := make(chan int)
17    go producer(ch)
18    //接收ch信道中的数据,直到该信道关闭。
19    for v := range ch {
20        fmt.Println(v)
21    }
22}

也可以自定for循环遍历信道

代码语言:javascript
复制
1for {
2        v, ok := <-ch //读操作
3        fmt.Println(v, ok)
4        if ok == false { //当读取不到数据跳出循环
5            break
6        }
7    }
(8)缓冲信道

语法结构 ch := make(chan type, cap) cap为容量。

•缓冲信道支持len()和cap() •只能向缓冲信道发送容量以内的数据 •只能接收缓冲信道长度以内的数据

信道是异步的,是一种在被创建时就被开辟了能存储一个或者多个值的信道。这种类型并不要求发送与接收同时进行。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。

代码语言:javascript
复制
 1func main() {
 2    //创建一个容量为3的缓冲信道
 3    ch := make(chan string, 3)
 4    ch <- "naveen"
 5    ch <- "paul"
 6    fmt.Println("capacity is", cap(ch))   //capacity is 3
 7    fmt.Println("length is", len(ch))     //length is 2
 8    fmt.Println("read value", <-ch)       //read value naveen
 9    fmt.Println("new length is", len(ch)) //new length is 1
10}
(9)WaitGroup

假设我们有 3 个并发执行的 Go 协程(由Go 主协程生成)。Go 主协程需要等待这 3 个协程执行结束后,才会终止。这就可以用 WaitGroup 来实现。

代码语言:javascript
复制
 1package main
 2
 3import (
 4    "fmt"
 5    "sync"
 6    "time"
 7)
 8
 9func process(i int, wg *sync.WaitGroup) {
10    fmt.Println("started Goroutine ", i)
11    time.Sleep(2 * time.Second)
12    fmt.Printf("Goroutine %d ended\n", i)
13    //Done方法减少WaitGroup计数器的值,应在线程的最后执行。
14    wg.Done()
15}
16
17/*
18    WaitGroup用于等待一组线程的结束。
19    父线程调用Add方法来设定应等待的线程的数量。
20    每个被等待的线程在结束时应调用Done方法。
21    同时,主线程里可以调用Wait方法阻塞至所有线程结束。
22*/
23func main() {
24    no := 3
25    var wg sync.WaitGroup
26    //并发协程
27    for i := 0; i < no; i++ {
28        /*
29            Add方法向内部计数加上delta,delta可以是负数;
30            如果内部计数器变为0,Wait方法阻塞等待的所有线程都会释放,
31            如果计数器小于0,方法panic。
32        */
33        wg.Add(1)
34        go process(i, &wg)
35    }
36    //Wait方法阻塞直到WaitGroup计数器减为0。
37    wg.Wait()
38    fmt.Println("over")
39}
(10)select

select 语句用于在多个发送/接收信道操作中进行选择。select 语句会一直阻塞,直到发送/接收操作准备就绪。

•如果有多个信道操作准备完毕,select 会随机地选取其中之一执行。 •空的select会触发死锁因此它会一直阻塞,导致死锁。

代码语言:javascript
复制
 1package main
 2import (
 3    "fmt"
 4    "time"
 5)
 6func server1(ch chan string) {
 7    time.Sleep(1 * time.Second)
 8    ch <- "from server1"
 9}
10func server2(ch chan string) {
11    time.Sleep(1 * time.Second)
12    ch <- "from server2"
13}
14func main() {
15    output1 := make(chan string)
16    output2 := make(chan string)
17    go server1(output1)
18    go server2(output2)
19    //随机选择
20    select {
21    case s1 := <-output1:
22        fmt.Println(s1)
23    case s2 := <-output2:
24        fmt.Println(s2)
25    }
26}

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-12-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Go是并发语言,而不是并行语言。
  • 一、并发和并行的区别
  • 二、Go协程(Goroutine)
  • (1)协程的创建
    • (2)创建多个协程
      • (3)调⽤ runtime.Goexit()将⽴即终⽌当前 goroutine 执⾏。但所有已注册 defer延迟调⽤会被被执⾏。
      • (4)调⽤ runtime.Gosched()将当前 goroutine 暂停,放回队列等待下次被调度执⾏。
      • 三、信道(Channel)
        • (1)信道的创建
          • (2)通过信道发送和接收数据
            • (3)发送和接收默认是阻塞的
              • (4)死锁
                • (6)单向信道与关闭信道close()
                • (7)遍历信道
                  • (8)缓冲信道
                    • (9)WaitGroup
                      • (10)select
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档