💨💨💨下面是go语言并发编程的学习笔记。
一、go语言的并发
进程和线程
进程:一个具有一定独立功能的程序关于 某个数据集合的一次运行活动。也就是说同一程序、同一数据。
线程:进程中的一条执行路线。
那么什么是并发呢?
A. 多线程程序在一个核的cpu上运行,就是并发。
B. 多线程程序在多个核的cpu上运行,就是并行。
go语言的并发(不是并行)
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:一个线程上可以跑多个协程,协程是轻量级的线程。
go语言通过 go 关键字来开启 goroutine 实现并发。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
二、goroutine
同一个程序中的所有 goroutine 共享同一个地址空间。
代码1:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
输出的 hello 和 world 是没有固定先后顺序。因为它们是两个 goroutine 在执行
代码2
package main
import (
"fmt"
)
func hello() {
fmt.Println("Hello Goroutine!")
}
func main() {
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
//time.Sleep(time.Second)
}
执行结果只打印了main goroutine done!,并没有打印Hello Goroutine!。为什么呢?
main函数的goroutine,在hello函数所在的goroutine执行hello函数之前结束了。(main函数的goroutine结束会关闭所有的goroutine。)
如果俺们需要执行hello函数,可以使用time.sleep。执行时会发现先打印main goroutine done。这是因为,创建新的goroutine会花费一些时间。
代码3:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
//add用来设置waitgroup的计数值
//done用来将waitgroup的计数减1
//wait调用这个方法的线程会一直阻塞,直到waitgroupe的值变为0
//waitgroup线程同步,指等待一个组执行完后,才会向下执行
//可以解决一个进程等待多个该进程启动的子线程都正常运行完成的场景
func hello(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go hello(i) //这里会创建新的线程
}
fmt.Println("main run!")
wg.Wait() // 等待所有登记的goroutine都结束
}
这里很像pv操作,wg是一般信号量,add相当于v操作,资源量+1;done相当于p操作,资源量-1,wait是等待。
就是在for循环创建新的协程,i=0,wg+1,创建一个新的协程,在新的协程运行hello函数,wg-1。(hello函数的内容是在打印Hello Goroutine! ,i之后将wg的计数减1。)i= 1,重复这个过程,i=9,wg+ 1,创建新的协程,在新的协程运行hello函数,wg-1。mian中调用wait,main阻塞直到wg = 0,直到登记的goroutine都结束。
注意:每次打印的数字的顺序都不一致。这是因为10个goroutine是并发执行的,而goroutine的调度是随机的。