并行和并发
项目地址 go-project-example
go就是基于并发比较diao的玩意,为了适应多核以达到并行
协程(Goroutine)
例子
-
快速打印例子
hello world : 4 hello world : 3 hello world : 2 hello world : 1 hello world : 0
for i := 0; i < 5; i++ {
go func(j int) { //i:协程共有,j:协程独有
println("hello world : " + fmt.Sprint(j))
}(i)
}
time.Sleep(time.Second) //1秒
小优化
Sleep() 等待线程结束不是好东西
使用协程同步的WaitGroup,这个玩意提供了三个方法,类似于wait() 等待协程,进行同步
本质就是计数器:
- 开启协程+1;
- 执行结束-1;
- 主协程阻塞直到计数器为0。
var wg sync.WaitGroup//声明
for i := 0; i < 5; i++ {
wg.Add(1)//+1
go func(j int) {
defer wg.Done()//-1
println("hello world : " + fmt.Sprint(j))
}(i)
}
wg.Wait()//阻塞主线程
协程同步
提倡通过通信共享内存而不是通过共享内存而实现通信
通道通信 (Channel)
ch :=make(chan 元素类型,[缓冲大小])//定义
ch <- i // 向ch发送数据
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v; (箭头的指向就是数据的流向)
defer close(ch) //会返回一个空的构造体
-
无缓冲通道 : make(chan int)
- 一个资源量,直接堵塞
-
有缓冲通道 : make(chan int,2)
- 就是生产者消费者模式,使用资源量进行一个协程同步的
func main() {
CalSquare()
time.Sleep(1 * time.Second)
}
func CalSquare() {
src := make(chan int) //生产者存放的通道
dest := make(chan int, 3) //消费者存放的通道
go func() {
defer close(src) // 协程关闭时关闭管道
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i //将生产者的数据发送到dest通道
}
}()
for i := range dest { //兼容打印速度,生成数据的速度快
println(i)
}
}
通道关闭事项:
生产者多将通道关闭了,消费者还是可以取数据
- 关闭通道是一种通知接收者不再有新数据发送的方式。
- 已关闭的通道仍然可以被读取,接收者可以继续读取通道中已经发送的数据,直到通道为空。
- 在从已关闭的通道接收完所有数据后,后续的接收操作会返回通道元素类型的零值,且不会阻塞。
- 尝试在已关闭的通道上发送数据将导致 panic,且不要关两次
两个通道
- 生产者的通道
src速度快- 消费者将数据取出发送到
dest通道dest通道 兼容打印速度,使用缓存队列
资源 (Sync)
对于共享的数据,使用lock加锁,只限于一个协程访问
var (
x int64
lock sync.Mutex //1、
)
func addWithLock() {//加锁
for i := 0; i < 2000; i++ {
lock.Lock() //2
x += 1
lock.Unlock()
}
}
func addWithoutLock() {//没有加锁
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println(x) //10000
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
fmt.Println(x) //6531
}