- 并行同时做不同事。 
- 并发交替做不同事。 
假设你需要洗衣服和做饭
- 串行:先洗完衣服再做饭,或者先做完饭再洗衣服。
- 并发:一会洗衣一会做饭。
- 并行:把洗衣盆拿到灶边,一只手做饭另一只手洗衣。
 为啥快 ⁉️
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异常。对一个已经被close的channel进行接收操作依然可以接受到之前已经成功发送的数据;如果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的内部缓存队列将是满的,如果现在有第四个发送操作将发生阻塞。

接收一个值,
fmt.Println(<-ch) // "A"

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

内置的cap函数获取channel内部缓存的容量。
fmt.Println(cap(ch)) // "3"
内置的len函数,获取channel内部缓存队列中有效元素的个数。
fmt.Println(len(ch)) // "2"
类比饭馆吃饭
要吃饭的人 <-
空位 chan
<-厨师
更多的使用更多的理解✋(充分利用cpu性能)。
到此我需要讲的语法都已经完成



Comments