简单聊一下并发模型,下一篇会单独全篇聊聊多种并发模型,以及其演进过程。
我们经常接触到的并发模型是多线程并发模型,而Go语言中的并发模型是CSP并发模型,这里简单介绍一这两种并发模型
多线程并发模型是在一个应用程序中同时存在多个执行流,这多个执行流通过内存共享,信号量,锁等方式进行通信,CPU在多个线程间进行上下文切换,从而达到并发执行,提高CPU利用率,其本质是内核态线程和用户态线程是一对一的关系
CSP并发模型的意思将程序的执行和通信划分开来(Process和Channel),Process代表了执行任务的一个单元,Channel用来在多个单元之间进行数据交互,共享;Process内部之间没有并发问题,所有由通信带来的并发问题都被压缩在Channel中,使得聚合在一起,得到了约束,同步,竞争聚焦在Channel上,Go就是基于这种并发模型的,Go在线程的基础上实现了这一套并发模型(MPG),线程之上虚拟出了协程的概念,一个协程代表一个Process,但在操作系统级别调度的基本单位依然是线程,只是Go自己实现了一个调度器,用来管理协程的调度,M(Machine)代表一个内核线程,P(Process)代表一个调度器,G(Goroutine)代表一个协程,其本质是内核线程和用户态线程成了多对多的关系
package main
func main(){
fmt.Println("主协程启动")
}
package main
func main(){
var wg sync.WaitGroup
wg.Add(1)
go func(){
defer wg.Done()
fmt.Println("新的协程启动")
}()
fmt.Println("主协程启动")
//等待新的协程运行完毕,程序才退出
wg.Wait()
}
//通道分为两类:
//无缓冲区的通道
c := make(chan int)
//有缓冲区的通道
c := make(chan int,10)
//通道的操作
//往通道写入数据
c <- 1
//从通道读取数据,
//temp是读取到的值
//ok是返回此通道是否已被关闭
temp,ok := <- c
//关闭通道
close(c)
//遍历通道
for v := range c{
}
package main
func main(){
var wg sync.WaitGroup
wg.Add(1)
//不带缓冲区的channel
c := make(chan string)
go func(){
defer func(){
wg.Done()
}()
for{
//从通道中取出数据
temp := <- c
if temp == "写入数据3" {
break
}
}
}()
//主协程循环往通道写入值
for i:=1;i<4;i++{
c <- "写入数据"+strconv.Itoa(i)
}
//等待新的协程运行完毕,程序才退出
wg.Wait()
}
//最终程序执行结果
/**
写入数据1
写入数据2
写入数据3
*/
/**生产者消费者的例子*/
func ProductAndConsumer() {
wg := sync.WaitGroup{}
wg.Add(1)
//带有缓冲区的通道
cint := make(chan int, 10)
go func() {
//product ,循环往通道中写入一个元素
for i := 0; i < 100; i++ {
cint <- i
}
//关闭通道
close(cint)
}()
go func() {
defer wg.Done()
//consumer 遍历通道消费元素并打印
for temp := range cint {
fmt.Println(temp)
//len函数可以查看当前通道元素个数
fmt.Println("当前通道元素个数",len(cint))
}
}()
wg.Wait()
}
package main
func main(){
c := make(chan int,10)
close(c)
c <- 1
}
//结果如下
panic: send on closed channel
package main
func main(){
c := make(chan int,10)
//从一个永远都不可能有值的通道中读取数据,会发生死锁,因为会阻塞主程序的执行
<- c
}
func main(){
c := make(chan int,10)
//主程序往一个没有消费者的通道中写入数据时会发生死锁, 因为会阻塞主程序的执行
c <- 1
}
//结果如下
fatal error: all goroutines are asleep - deadlock!
package main
func main(){
c1 := make(chan int)
c2 := make(chan int)
go func(){
c1 <- 1
c2 <- 2
}()
<- c2
}
//结果
fatal error: all goroutines are asleep - deadlock!
通道死锁的一些注意事项,其实上面的死锁情况主要分为如下两种
//select基本用法
select {
case <- c1:
// 如果c1成功读到数据,则进行该case处理语句
case c2 <- 1:
// 如果成功向c2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
package main
func queryUserById(id int)chan string{
c := make(chan string)
go func(){
c <- "姓名"+strconv.Itoa(id)
}()
return c
}
func main(){
//三个协程同时并发查询,缩小执行时间,
//本来一次查询需要1秒,顺序执行就得3秒,
//现在并发执行总共1秒就执行完成
name1 := queryUserById(1)
name2 := queryUserById(2)
name3 := queryUserById(3)
//从通道中获取执行结果
<- name1
<- name2
<- name3
}
package main
func queryUserById(id int)chan string{
c := make(chan string)
go func(){
c <- "姓名"+strconv.Itoa(id)
}()
return c
}
func main(){
c1, c2, c3 := queryUserById(1), queryUserById(2), queryUserById(3)
c := make(chan string)
// 开一个goroutine监视各个信道数据输出并收集数据到信道c
go func() {
for {
// 监视c1, c2, c3的流出,并全部流入信道c
select {
case
v1 := <- c1:
c <- v1
case
v2 := <- c2:
c <- v2
case
v3 := <- c3:
c <- v3
}
}
}()
// 阻塞主线,取出信道c的数据
for i := 0; i < 3; i++ {
// 从打印来看我们的数据输出并不是严格的顺序
fmt.Println(<-c)
}
}
func main() {
c, quit := make(chan int), make(chan int)
go func() {
c <- 2 // 添加数据
quit <- 1 // 发送完成信号
} ()
for is_quit := false; !is_quit; {
// 监视信道c的数据流出
select {
case v := <-c: fmt.Printf("received %d from c", v)
case <-quit: is_quit = true
// quit信道有输出,关闭for循环
}
}}
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有