- 并行同时做不同事。
- 并发交替做不同事。
假设你需要洗衣服和做饭
- 串行:先洗完衣服再做饭,或者先做完饭再洗衣服。
- 并发:一会洗衣一会做饭。
- 并行:把洗衣盆拿到灶边,一只手做饭另一只手洗衣。
为啥快 ⁉️
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)
复制一个channe
l或用于函数参数传递时,只是拷贝了一个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