Go语言进阶系列 | 青训营笔记

48 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

Go语言并发编程

1、Goroutine(Go语言是如何实现并发的)

前置知识:

  • 并行与并发
  • 进程与线程
  • Go语言通过协程实现并发
  • 多个协程之间是并发执行的
  • 一个线程上可以跑很多个协程

image.png

2、创建协程

在被调用的函数前面加 go 关键字,表示这是一个协程 下边代码展示了利用协程打印0~4

package main

import (
	"fmt"
	"time"
)

func main() {
	for i := 0; i < 5; i++ {
		go hello(i)
	}
	time.Sleep(time.Second) //让主函数睡一秒,否则主函数结束后,其中包含的协程也会自动停止
}

func hello(i int) {
	println("hello:" + fmt.Sprint(i))
}

运行结果:

image.png

3、通信和共享内存

官方建议通过通信实现共享内存,而不是通过共享内存实现通信

image.png

如何理解呢?

通信实现共享内存即:协程之间通过管道传递信息,以此实现多个协程共享某些变量或资源,只有协程1将变量a传递给协程2,才允许协程2访问变量a

共享内存实现通信即:多个协程可以共同访问某个全局变量,通过全局变量实现协程之间的 通信

Go语言对上述俩种方式均支持,但更推荐前者

4、channel(通信实现共享内存)

通过make创建一个管道,需要指定管道类型,大小可选(不写即大小为1,也就是不带缓冲区的管道)。 协程之间通过管道通信

image.png

一个简单的生产者消费者模型

package main

import (
	"fmt"
)

func main() {
	chan1 := make(chan int)
	chan2 := make(chan int, 3)

	//生产者
	go func() {
		defer close(chan1)
		for i := 0; i < 10; i++ {
			chan1 <- i
		}
	}()

	//消费者
	go func() {
		defer close(chan2)
		for i := range chan1 {
			chan2 <- i * i
		}
	}()

	//遍历chan2
	for i := range chan2 {
		fmt.Println(i)
	}
}

关于defer的用法参考:深入了解golang中的defer关键字 - 掘金 (juejin.cn)

5、Lock(共享内存实现通信)

通过对全局变量加锁,实现多个协程互斥访问变量x

package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	x    int
	lock sync.Mutex
)

func main() {
	for i := 0; i < 10; i++ {
		go addWithLock()
	}
	time.Sleep(time.Second) //保证10个协程跑完
	fmt.Println(x) //输出10000
	x = 0
	for i := 0; i < 10; i++ {
		go addWithOutLock()
	}
	time.Sleep(time.Second)
	fmt.Println(x) //小于10000
}

//加锁
func addWithLock() {
	for i := 0; i < 1000; i++ {
		lock.Lock()
		x++
		lock.Unlock()
	}
}

//不加锁
func addWithOutLock() {
	for i := 0; i < 1000; i++ {
		x++
	}
}

6、WaitGroup

通过三个基本操作控制协程:

  • Add(delta int) 计数器+delta
  • Done() 计数器-1
  • Wait() 阻塞(一直到计数器为0)

实例:

package main

import (
	"fmt"
	"sync"
)

var (
	wg sync.WaitGroup
)

func main() {
	wg.Add(5) //计数器加5
	for i := 0; i < 5; i++ {
		go hello(i)
	}
	wg.Wait() //阻塞当前协程,直到计数器为0
}

func hello(i int) {
	println("hello:" + fmt.Sprint(i))
	wg.Done() //计数器减1
}