Go中的并发编程
这是我参与【第五届青训营】伴学笔记创作活动的第16天
1.Goroutine:
使用goroutine
- Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。
- 一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。
GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。
- 1.G很好理解,就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
- 2.P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
- 3.M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的;
2.runtime包:
runtime.Gosched():
- 让出CPU时间片,重新等待安排任务
func main() {
go func(s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}("world")
// 主协程
for i := 0; i < 2; i++ {
// 切一下,再次分配任务
runtime.Gosched()
fmt.Println("hello")
}
}
runtime.Goexit():
- 退出当前协程
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
// 结束协程
runtime.Goexit()
defer fmt.Println("C.defer")
fmt.Println("B")
}()
fmt.Println("A")
}()
for {
}
}
runtime.GOMAXPROCS
- Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。
3.Channel:
3.1 channel是一种类型,一种引用类型。声明通道类型的格式如下:
var 变量 chan 元素类型
//例如:
var ch1 chan int // 声明一个传递整型的通道
- 通道是引用类型,通道类型的空值是nil。
3.2 声明的通道后需要使用make函数初始化之后才能使用。
make(chan 元素类型, [缓冲大小])
ch4 := make(chan int)
3.3 channel操作:
- 具有发送(send),接收(receive),关闭(close)三种操作
ch <- 10 //把10发送到ch
x := <- ch //从ch中接收并赋值给x
close(ch) //关闭通道
3.4 有缓冲的通道
我们可以在使用make函数初始化通道的时候为其指定通道的容量,例如:
func main() {
ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
ch <- 10
fmt.Println("发送成功")
}
4.定时器:
func main() {
// 1.timer基本使用
timer1 := time.NewTimer(2 * time.Second)
t1 := time.Now()
fmt.Printf("t1:%v\n", t1)
t2 := <-timer1.C
fmt.Printf("t2:%v\n", t2)
// 2.验证timer只能响应1次
timer2 := time.NewTimer(time.Second)
for {
<-timer2.C
fmt.Println("时间到")
}
// 3.timer实现延时的功能
//(1)
time.Sleep(time.Second)
//(2)
timer3 := time.NewTimer(2 * time.Second)
<-timer3.C
fmt.Println("2秒到")
//(3)
<-time.After(2*time.Second)
fmt.Println("2秒到")
// 4.停止定时器
timer4 := time.NewTimer(2 * time.Second)
go func() {
<-timer4.C
fmt.Println("定时器执行了")
}()
b := timer4.Stop()
if b {
fmt.Println("timer4已经关闭")
}
// 5.重置定时器
timer5 := time.NewTimer(3 * time.Second)
timer5.Reset(1 * time.Second)
fmt.Println(time.Now())
fmt.Println(<-timer5.C)
for {
}
}
5.并发安全和锁:
5.1 互斥锁:
var x int64
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
5.2 读写互斥锁
- 读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。
var (
x int64
wg sync.WaitGroup
lock sync.Mutex
rwlock sync.RWMutex
)
func write() {
// lock.Lock() // 加互斥锁
rwlock.Lock() // 加写锁
x = x + 1
time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
rwlock.Unlock() // 解写锁
// lock.Unlock() // 解互斥锁
wg.Done()
}
func read() {
// lock.Lock() // 加互斥锁
rwlock.RLock() // 加读锁
time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
rwlock.RUnlock() // 解读锁
// lock.Unlock() // 解互斥锁
wg.Done()
}
func main() {
start := time.Now()
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}
6.Sync:
6.1 sync.WaitGroup:
- Go语言中可以使用sync.WaitGroup来实现并发任务的同步
- (wg *WaitGroup) Add(delta int) : 计数器+delta
- (wg *WaitGroup) Done() : 计数器-1
- (wg *WaitGroup) Wait() : 阻塞直到计数器变为0
var wg sync.WaitGroup
func hello() {
defer wg.Done()
fmt.Println("Hello Goroutine!")
}
func main() {
wg.Add(1)
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
wg.Wait()
}
6.2 sync.Once
var icons map[string]image.Image
var loadIconsOnce sync.Once
func loadIcons() {
icons = map[string]image.Image{
"left": loadIcon("left.png"),
"up": loadIcon("up.png"),
"right": loadIcon("right.png"),
"down": loadIcon("down.png"),
}
}
// Icon 是并发安全的
func Icon(name string) image.Image {
loadIconsOnce.Do(loadIcons)
return icons[name]
}
6.3 sync.Map:
- Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。
var m = sync.Map{}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 20; i++ {
wg.Add(1)
go func(n int) {
key := strconv.Itoa(n)
m.Store(key, n)
value, _ := m.Load(key)
fmt.Printf("k=:%v,v:=%v\n", key, value)
wg.Done()
}(i)
}
wg.Wait()
}