2018-07-19 17:16:40

goweb-036-golang-Goroutines和Channels

  • 并行同时做不同事。
    alt
  • 并发交替做不同事。
    alt

假设你需要洗衣服和做饭

  1. 串行:先洗完衣服再做饭,或者先做完饭再洗衣服。
  2. 并发:一会洗衣一会做饭。
  3. 并行:把洗衣盆拿到灶边,一只手做饭另一只手洗衣。
    为啥快 ⁉️

Goroutines 协程

  • 传统的多线程模型中创建一个新的线程代价高昂8M
  • Go语言中,每一个并发的执行单元叫作一个goroutine(协程)。类比轻量级的线程2kb
  • 通过在普通函数前添加go直接启动新的协程执行。 更多细节请 https://golang.google.cn/ref/mem 官网走一波

用示例说明
传统模式

package main

import (
    "fmt"
    "time"
)

func fn1() {
    time.Sleep(1 * time.Second)
    fmt.Println("暂停 1 s")
}
func fn2() {
    time.Sleep(2 * time.Second)
    fmt.Println("暂停 2 s")
}
func main() {
    //开始时间
    begin := time.Now()
    for i := 0; i < 5; i++ {
        fn1()
        fn2()
    }
    //获取运行结束时间
    end := time.Now()
    //输出时间差
    fmt.Println("总共用时:", end.Sub(begin)) //总共用时: 15.0871782s
}

并发模式

直接添加go开启新的协程 main函数的主协程并不会等待子协程结束需改造

package main

import (
    "fmt"
    "sync"
    "time"
)

func fn1() {
    time.Sleep(1 * time.Second)
    fmt.Println("暂停 1 s")
    // 执行完成就关闭一个等待
    wg.Done()
}
func fn2() {
    time.Sleep(2 * time.Second)
    fmt.Println("暂停 2 s")
    // 执行完成就关闭一个等待
    wg.Done()
}

//WaitGroup 可以用来等待协程执行完成
var wg sync.WaitGroup

func main() {
    //开始时间
    begin := time.Now()
    for i := 0; i < 5; i++ {
        go fn1()
        // 每次启动一个 协程
        wg.Add(1)
        go fn2()
        wg.Add(1)
    }
    //等待所有子协程执行完成
    wg.Wait()
    //获取运行结束时间
    end := time.Now()
    //输出时间差
    fmt.Println("总共用时:", end.Sub(begin)) //总共用时: 2.015722s
}

时间就是金钱☢️

Channels

  • channels则是goroutine之间的通信机制。一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。
  • 每个channel都有一个特殊的类型,也就是channel可发送数据的类型。
  • 一个可以发送int类型数据的channel一般写为chan int

    抽象的东西往往不好理解,可以理解为一个队列的引用。

内置的make函数,我们可以创建一个channel:

ch := make(chan int) 

复制一个channel或用于函数参数传递时,只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。
和其它的引用类型一样,channel的零值也是nil。

  • 一个channel有发送和接受两个主要操作(入队✍️出队)。
  • 发送和接收两个操作都使用<-运算符。
  • 在发送语句中,<-运算符分割channel和要发送的值。
  • 在接收语句中,<-运算符写在channel对象之前。
ch <- 10  // 发送
x := <-ch // 接收
<-ch

Channel支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被closechannel进行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话将产生一个零值的数据。

ch := make(chan int)
ch <- 10 // 发送
close(ch)
x := <-ch
// val, ok:= <- ch
fmt.Println(x)

以最简单方式调用make函数创建的是一个无缓存的channel,也可以指定第二个整型参数,对应channel的容量。如果大于零,那么该channel就是带缓存的channel

ch = make(chan int)    
ch = make(chan int, 0) //0 也没有缓冲
ch = make(chan int, 3) 

不带缓存的Channels

  • 无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作。
  • 如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。
  • 当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。

case1 阻塞

ch := make(chan int)
x := <-ch
fmt.Println(x)//fatal error: all goroutines are asleep - deadlock!

case2 阻塞

ch := make(chan int)
ch <- 100
x := <-ch
fmt.Println(x)//fatal error: all goroutines are asleep - deadlock!

case3 不阻塞

ch := make(chan int)
go func() {
    ch <- 100
}()
x := <-ch
fmt.Println(x)

带缓存的Channels

  • 带缓存的Channel内部持有一个元素队列。
  • 队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。
    ch = make(chan string, 3)
    

在无阻塞的情况下连续向新创建的channel发送三个值 不阻塞

ch <- "A"
ch <- "B"
ch <- "C"

现在channel的内部缓存队列将是满的,如果现在有第四个发送操作将发生阻塞。

alt

接收一个值,

fmt.Println(<-ch) // "A"

alt
此时对channel执行的发送或接收操作都不会发生阻塞。

内置的cap函数获取channel内部缓存的容量。

fmt.Println(cap(ch)) // "3"

内置的len函数,获取channel内部缓存队列中有效元素的个数。

fmt.Println(len(ch)) // "2"

类比饭馆吃饭
要吃饭的人 <-
空位 chan
<-厨师
更多的使用更多的理解✋(充分利用cpu性能)。

到此我需要讲的语法都已经完成

本文链接:https://www.paishouba.com/post/goweb-03-6.html

-- EOF --

Comments