Go并发笔记
并行是实现并发的一个手段
并发:宏观上并行,微观交替执行。简单举例是多线程程序在一个核的CPU上运行
并发:真正实现同时运行,需要空间支持,比如多线程程序在多个核的cpu上同时运行
Go可以充分发挥多核优势
协程Goroutine:用户态,轻量级线程,栈KB级别
线程:内核态,线程跑多个协程,栈式MB级别
- 举例:利用协程可以比for循环更快速的打印循环的效果
func hello(i int) {
println("hello goroutine :" + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i ++ {
go func(j int) { //函数前加关键字go就会使得函数以协程来运行
hello(j)
}(i)
}
time.Sleep(time.Second) //阻塞,保证协程执行完之前主线程不退出
}
协程之间的通信:CSP
提倡通过通信去共享内存,而不是通过共享内存实现通信
Channel
make(chan 元素类型,[缓冲区大小])
- 无缓冲通道 make(chan int) 同步通道
- 有缓冲通道 make(chan int, 2)
举例:A协程产生数据,B协程计算数据的平方,可以保证顺序性
func CalSquare() { //类似于生产者消费者模型
src := make(chan int)
dest := make(chan int, 3)
go func() { //A协程
defer close(src)
for i := 0; i < 10; i ++ {
src <- i
}
}()
go func() { //B协程
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest { //主协程
println(i)
}
}
defer:
-
defer一般用于资源的释放和异常的捕捉, 作为Go语言的特性之一.
-
defer 语句会将其后面跟随的语句进行延迟处理. 意思就是说 跟在defer后面的语言 将会在程序进行最后的return之后再执行.
-
在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
并发安全Lock
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 Add() {
x = 0
for i:= 0; i < 5; i ++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println(x) //8382
x = 0
for i:= 0; i < 5; i ++ {
go addWithLock()
}
println(x) //10000
}
这里其实就类似于多线程编程了,这里运用了互斥锁mutex,lock代表上锁,上锁之后其他线程无法访问临界区,unlock之后可以访问临界区,防止多个线程(这里是协程)同时访问同一个内存单元导致结果不正确。
WaitGroup
Add(delta int) 计数器+delta,一共delta个任务
Done() 每执行完一个任务,计数器-1
Wait() 阻塞直到计数器为0
func hello(i int) {
println("hello goroutine :" + fmt.Sprint(i))
}
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i:=0; i < 5; i ++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
这段代码先创造了5个任务,调用wait表示这5个任务若还没有执行完,则不会退出
go启动了协程,一共启动5个协程
defer表示只有在该协程执行完hello之后才会关闭该协程
wait则保证了所有协程结束前主函数不会停止。