并发VS并行
并发是指多个线程运行在一个核的CPU上运行
并行是指多个线程运行在多个核的CPU上
Goroutine
协程:用户态,轻量级线程,栈KB级别
线程:内核态,线程跑多个协程,栈MB级别
使用方式: 快速打印hello goroutine
func hello(i int) {
fmt.Println("hello goroutine : " + fmt.Sprint(i))
}
func main() {
for i := 0; i < 5; i++ {
//使用go关键字开启
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
CSP
提倡通过通信共享内存而不是通过共享内存而实现通信
Channel
make(chan 元素类型,[缓冲大小])
- 无缓冲通道 make(chan int) 同步通道
- 有缓冲通道 make(chan int,2) 异步通道
一个案例:
A子协程发送0~9数字
B子协程计算输入数字的平方
主协程输出最后的平方数
package main
func main() {
src := make(chan int)
dest := make(chan int, 3)
//A子协程
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
//B子协程
go func() {
defer close(dest)
for i := 0; i < 10; i++ {
dest <- i * i
}
}()
//主协程
for i := range dest {
println(i)
}
}
并发安全Lock
go中的lock是sync.Mutex类型
package main
import (
"fmt"
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
// 使用锁进行累加
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func main() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
fmt.Println("不使用锁:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println("使用锁:", x)
}
最后的输出结果
不使用锁: 7363
使用锁: 10000
可以看到多个协程是会出现并发安全的问题的
WaitGroup
之前阻塞协程一直使用的是Sleep,但是一个协程执行的实现可能小于设置的时间。Go语言提供了一个WaitGroup来精确的获取一个协程的执行时间
其实就是一个计数器,开启协程+1,执行结束-1,主协程阻塞直到计数器为0
对前面的demo进行优化
package main
import (
"fmt"
"sync"
)
var (
x int64
lock sync.Mutex
wg sync.WaitGroup
)
// 使用锁进行累加
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
wg.Done()
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
wg.Done()
}
func main() {
x = 0
wg.Add(5)
for i := 0; i < 5; i++ {
go addWithoutLock()
}
wg.Wait()
fmt.Println("不使用锁:", x)
x = 0
wg.Add(5)
for i := 0; i < 5; i++ {
go addWithLock()
}
wg.Wait()
fmt.Println("使用锁:", x)
}