- 并行同时做不同事。

- 并发交替做不同事。

假设你需要洗衣服和做饭
- 串行:先洗完衣服再做饭,或者先做完饭再洗衣服。
- 并发:一会洗衣一会做饭。
- 并行:把洗衣盆拿到灶边,一只手做饭另一只手洗衣。
为啥快 ⁉️
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