3.22 Go之CSP:通信顺序进程
Go的并发模型
-
多线程共享内存--->
java
或c++
等语言中的多线程开发 -
CSP
(Communicating sequential processes
)--->两个独立并发实体通过channel
进行通信的并发模型
值得注意的是:
-
Go
并没有完全实现CSP
仅仅只是实现了process
--->goroutine
和channel
这两个概念 -
是并发不是并行,并发的程序可以顺序执行,在真正的多核
CPU
上才可能真正地同时运行 -
并发编程,对共享资源正确访问要精确控制
Go
则是将共享值通过通道传递
并发编程的核心
同步通信
同步通信在Go中的举例
示例一:
package main
import (
"fmt"
"sync"
)
/*
Go并发编程之同步通信
*/
func main() {
// 声明互斥量
var mu sync.Mutex
// 开启协程加锁
go func() {
// 打印结果
fmt.Println("正在加锁!")
mu.Lock()
}()
// 进行解锁
mu.Unlock()
}
/*
代码分析:
由于 mu.Lock() 和 mu.Unlock() 并不在同一个 Goroutine 中,所以也就不满足顺序一致性内存模型。同时它们也没有其他的同步事件可以参考,也就是说这两件事是可以并发的。
因为可能是并发的事件,所以 main() 函数中的 mu.Unlock() 很有可能先发生,而这个时刻 mu 互斥对象还处于未加锁的状态,因而会导致运行时异常。
*/
解决上述运行异常问题:
package main
import (
"fmt"
"sync"
)
/*
修复上午的问题
*/
func main() {
// 声明一个互斥锁量
var mu sync.Mutex
// 先加一层锁
mu.Lock()
// 开启一个协程
go func() {
fmt.Println("加锁中!")
// 解锁
mu.Unlock()
}()
// 再次锁定
mu.Lock()
}
/*
当第二次加锁时会因为锁已经被占用(不是递归锁)而阻塞,main() 函数的阻塞状态驱动后台线程继续向前执行。
后台线程执行到 mu.Unlock() 时解锁,此时打印工作已经完成了,解锁会导致 main() 函数中的第二个 mu.Lock() 阻塞状态取消
此时后台线程和主线程再没有其他的同步事件参考,它们退出的事件将是并发的,在 main() 函数退出导致程序退出时,后台线程可能已经退出了,也可能没有退出。虽然无法确定两个线程退出的时间,但是打印工作是可以正确完成的。
*/
使用无缓存通道实现同步
明确Go
内存模型规范:
-
对
Go
的无缓存通道:通道接收发生在对通道进行发送完成之前
package main
import "fmt"
/*
采用通道的形式进行:
注意Go的内存模型:
1、通道的接收发生在通道的发送之前
*/
func main() {
// 声明一个完成通道
done := make(chan int)
// 开启一个协程
go func() {
// 打印内容
fmt.Println("打印中!")
// 通道匿名接收
<-done
}()
// 然后再进行通道发送
done <- 1
}
注意:
对通道的缓存大小太敏感,如果通道有缓存,就无法保证main()
函数退出之前后台线程能正常打印,更好的做法是将通道的发送和接收方向调换一下
使用带缓存通道实现同步
解决办法:
采用带缓冲通道
package main
import "fmt"
/*
采用缓冲通道解决Go内存模型引起的通道接收发送问题
*/
func main() {
// 声明通道
done := make(chan int, 1)
// 开启一个协程
go func() {
// 打印
fmt.Println("打印中!")
// 此时先往通道当中写入
done <- 1
}()
// 再次从通道当中读取--->注意匿名读取
<-done
}
解析:
因为Go
当中存在带缓冲的通道那么第一次接收发生再缓冲通道填完之后.所以fmt
打印语句能够执行
基于带缓存通道的这些特性,故可以扩展打印线程
package main
import "fmt"
/*
使用带缓冲通道实现同步
*/
func main() {
// 声明一个带缓冲通道
done := make(chan int, 10)
// 开启带缓冲通道缓冲区数量的协程
for i := 0; i < cap(done); i++ {
// 开启协程
go func() {
fmt.Println("打印中!")
// 写入通道
done <- 1
}()
}
// 依次冲通道接收
for i := 0; i < cap(done); i++ {
<-done
}
}
简单的做法实现同步--->使用等待组:
package main
import (
"fmt"
"sync"
)
/*
采用等待组的方法实现同步操作
*/
func main() {
// 声明等待组
var wg sync.WaitGroup
// 开启N个打印线程
for i := 0; i < 10; i++ {
// 每开启一个添加一个计数器
/*
wg.Add(1)用于增加等待事件的个数
必须确保在后台线程启动之前执行(如果放到后台线程之中执行则不能保证被正常执行到)
*/
wg.Add(1)
// 开启协程
go func() {
fmt.Println("打印中!")
// 等待组处理
wg.Done()
}()
}
// 等待组结束
wg.Wait()
}
It's a lonely road!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!