这是我参与「第五届青训营 」伴学笔记创作活动的第 2天
工程实践
1.并发与并行
并发是指多线程程序在单核cpu上运行,通过 时间片切换 实现同时运行状态,宏观上并发,微观上交替
并行是指多线程程序在多核cpu上运行,go语言实现了并发性能提高的调度模型,go语言为并发而生。
2.Goroutine(协程)
线程:内核态、线程可以跑多个协程,栈MB级别
协程:用户态、轻量级线程、栈KB级别
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) //是子协程未完成之前,主协程不退出
}
协程的方式 对for循环 输出的结果是 乱序 的,因为是 HelloGoRoutine 是通过并行的方式输出,但是 速度快
3.CSP(Communicating Sequential Processes)
在go语言中提倡通过通信实现共享内存而不是通过共享内存实现通信
通过通信实现共享内存是通过通道的形式对协程进行连接,在数据传输时遵行先入先出,能保证收发数据有序性
通过共享内存实现通信必须通过互斥量对内存进行加锁,协程需要获取临界区的权限,不同的协程之间会容易产生数据静态的问题,可能会影响程序性能。
4.Channel(通道)
chan是引用类型,需要make关键字进行创建
make(chan 元素类型, [缓冲大小])//创建无缓冲通道
make(chan int)//创建无缓冲通道
make(chan int, 2)//创建有缓冲通道
无缓冲通道发送和接收同步进行,也被称为同步通道,解决同步问题就是使用有缓冲通道
样例
- A子协程发送0-9数字
- B子协程计算输入数字的平方
- 主协程输出
func CalSquare() {
src := make(chan int)// 创建src无缓冲通道
dest := make(chan int, 3)// 创建dest有缓冲通道
go func() { //A协程
defer close(src) // defer延迟资源关闭
for i := 1; i < 10; i++ {
src <- i //把数字发送到src通道
}
}()
go func() { //B协程
defer close(dest)
for i := range src { // 这里通过src通道实现了AB协程的通信
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
可以保证结果的顺序性,即并发安全
5.并发安全Lock
//实现1-2000相加
var(
x int64
lock sync.Mutex
)
//未加锁
func addWithoutLock(){
for i := 0; i <2000; i++ {
x += 1
}
}
//通过加锁解锁并发安全问题
func addWithLock(){
for i := 0; i <2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
//在线程中创建多个协程并发的运行
func add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
println(x)// 随机未知结果 不安全
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
println(x)//10000 结果正确
}
6.WaitGroup
对比标题2,time.Sleep实现并发同步时不够优雅,WaitGroup可以优雅的实现并发任务的同步,提供了三个方法
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()// 方法三 阻塞直到计数为0
}