并发编程【深度解剖】

并发介绍

谈到并发 ,随之而来的就是那几个问题。并发 并行 线程 进程

注意!!!本篇文章更多用诙谐的语调讲解,为保证易于理解,不够官方正式,所以可以结合AI读本篇文章,并且本文是以 go语言 的角度来看问题的。(~ ̄▽ ̄)~,祝大家收获满满。

进程与线程
复制代码
1、进程是程序在操作系统进行的一次操作过程
2、线程是进程的一个执行实体(线程就相当于进程派出的小兵,可以独立执行任务)
3、一个进程可以创建和撤销多个线程(可以同时派出多个小兵、也可以同时让士兵们撤退)
并发与并行
复制代码
`并发` 相当于一群人喝一瓶饮料,但是只有一根吸管,于是乎大家轮流喝。
`并行` 相当于一群人喝一瓶饮料,但是大家富有了,于是又买了几根吸管,
这时大家可以一起喝。

并发:

并行:

线程与协程
复制代码
1、线程与协程之间的关系,类似线程与协程之间的关系,在一个线程上可以跑多个协程。
2、由一个线程发起的协程,就像一个连队,共享一个堆空间(共享信息、如类、对象...等等),
而每个线程又有独立的栈空间。

Goroutine特性

复制代码
通信共享内存!
初次听到这个,你可能有点疑惑,啥是通信共享内存?
这里的 "内存" ,并不是大家明面意思上理解的,对同一片内存区域进行操作。
你可以简答的理解为旋转小火锅内的一盘菜,大家轮流拿着下火锅(通过channel实现)。
如果盘子里没菜了,那说明菜被吃完了,信息自然也就无法共享了。

这里对标的是,Java、C++等一系列语言的"内存共享通信",
他们才是对同一片内存区域进行操作,故需要进行繁琐的加锁去锁。
简单应用

只需要 go + 要调用的函数 即可(如下)。

go 复制代码
func hello() {
    fmt.Println("Hello Goroutine!")
}
func main() {
    go hello() // go + 要调用的函数
    fmt.Println("main goroutine done!")
}
Goroutine调度
复制代码
GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。
区别于操作系统调度OS线程。
接下来详细解释一下GPM中,分别对应什么:

G:对应着一个goroutine协程,内部存着本goroutine的信息,以及与其绑定P的信息

P:P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境。

M:是Go运行时(runtime)对操作系统内核线程的虚拟。与内核线程一一映射。

接下来,这个这张图片可以更好的描述

优势

看到这里,相信大家已经看到go语言实现并发的优点

其他语言要实现这一点,需要手搓一个线程池(进程、线程),但是go真正实现了通过一个gorountine走天下。

runtime包

runtime包在并发领域,具有代表性的三个函数。

复制代码
runtime.Gosched(),让出CPU时间片,重新等待安排任务

runtime.Goexit(),退出当前协程

runtime.GOMAXPROCS,Go运行时的调度器使用GOMAXPROCS参数
来确定需要使用多少个OS线程来同时执行Go代码

当只设置一个核心的时候,并配合让权等待,就能完美实现,交替打印。

go 复制代码
package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 1; i < 10; i++ {
		fmt.Println("A:", i)
		runtime.Gosched()
	}
}

func b() {
	for i := 1; i < 10; i++ {
		fmt.Println("B:", i)
		runtime.Gosched()
	}
}

func main() {
	runtime.GOMAXPROCS(1)
	go a()
	go b()
	time.Sleep(time.Second)
}
复制代码
记忆犹新,记得当时我调代码遇到一个问题->我把runtime.Goexit()放在main函数中
结果报了一个死锁的问题。
What???main函数死锁干嘛?他又不是协程???就算是协程,也不因该死锁呀???
我记得当时学操作系统时,死锁的四大条件分别是:
1、互斥条件
2、请求和保持条件
3、不可剥夺条件
4、循环等待条件
可是这些都跟main函数中放runtime.Goexit()没关系呀?

在 Go 里,程序要结束,main 函数所在的 Goroutine 得正常返回。要是在 main 函数中调用 runtime.Goexit(),main 函数就不会正常返回,这会让 Go 运行时觉得程序还在运行

要是除 main 函数的 Goroutine 之外没有其他活跃的 Goroutine 了,Go 运行时就会判定出现了死锁。这是因为程序既无法继续执行(无活跃的 Goroutine),又不能正常结束(main 函数未正常返回)。(正常的死锁就是这个条件)

扩展

深入了解runtime包,会发现其在go语言运行时,起着至关重要的作用

大家可以先尝试理解一下,下方这串文本

复制代码
_rt0_amd64_linux (汇编入口)
  -> runtime.rt0_go (Go 入口)
      -> runtime.args (解析参数)
      -> runtime.osinit (初始化 OS 信息)
      -> runtime.schedinit (初始化调度器)
          -> 初始化堆、GC、P 等
      -> 创建主 Goroutine
          -> runtime.main()
              -> 执行所有 init()
              -> main.main()
  -> runtime.mstart (启动调度循环)

其实我想放出这段文本的根本原因,是因为我发现了一个好玩的东西。

go语言,执行程序的方式,很有趣。

Channel

单纯执行函数之间的并发是没有意思的。

重点!单纯的函数之间的并发是没有意思的!!!

函数与函数间需要交换数据才能体现并发执行函数的意义

所以这里会用到channel,也就是管道。

具体关于管道的知识,等咱在抽空写一篇。


借鉴博客:

1、深入浅出 Go 语言的 GPM 模型(Go1.21)


相关推荐
wowocpp1 小时前
spring boot Controller 和 RestController 的区别
java·spring boot·后端
后青春期的诗go1 小时前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(二)
开发语言·后端·rust·rocket框架
freellf2 小时前
go语言学习进阶
后端·学习·golang
全栈派森4 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse4 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭5 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架5 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱5 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
☞无能盖世♛逞何英雄☜5 小时前
Flask框架搭建
后端·python·flask
进击的雷神5 小时前
Perl语言深度考查:从文本处理到正则表达式的全面掌握
开发语言·后端·scala