Go 并发(协程,通道,锁,协程控制)

一.协程(Goroutine)

并发:指程序能够同时执行多个任务的能力,多线程程序在一个核的cpu上运行,就是并发。

并行:多线程程序在多个核的cpu上运行,就是并行。

并发主要由切换时间片来实现"同时"运行,并行则是直接利用多核实现多线程的运行,go可以设置使用核数,以发挥多核计算机的能力。

协程:

  •  Go 中的并发执行单位,类似于轻量级的线程。

  • Goroutine 的调度由 Go 运行时管理,用户无需手动分配线程。

  • 使用 go 关键字启动 Goroutine。

  • Goroutine 是非阻塞的,可以高效地运行成千上万个 Goroutine。

  • 在main()函数调用时就开启了一个主协程,主协程停止则其他的由主协程创建的分协程也停止

  • OS线程(操作系统线程)具有栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB

协程的好处:

  • 轻量级:协程的创建和切换开销非常小,可以在一个程序中创建大量协程。
  • 自动调度:协程由 Go 运行时自动调度,开发者不需要手动管理线程的创建和销毁。
  • 共享内存:协程之间可以共享相同的地址空间,简化了内存管理和数据共享。
  • 非阻塞:协程之间的通信和同步是非阻塞的,避免了传统线程中的锁竞争问题。

协程与线程的区别:

协程:

  1. 非操作系统提供而是由用户自行创建和控制的用户态‘线程’,比线程更轻量级。
  2. 在一个Go程序中同时创建成百上千个goroutine是非常普遍的,一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB

线程:

  1. 由操作系统管理,创建和切换开销较大,适用于需要高性能和复杂调度的场景。
  2. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

问题:

通常主协程运行的速度大于创建的分协程,导致程序运行完了,分协程还没运行就结束了

解决方法:

       1.通过time包中sleep函数让主协程睡眠一段时间,但是睡眠时间是固定的从而分协程已经运行完但是主协程还在睡眠。

       2.通常在main goroutine 中使用sync.WaitGroup来等待 其他goroutine 完成后再退出。

package mainimport ("fmt""sync"
)// 声明全局等待组变量
var wg sync.WaitGroupfunc hello() {fmt.Println("hello")wg.Done() // 告知当前goroutine完成
}func main() {wg.Add(1) // 登记1个goroutinego hello()fmt.Println("你好")wg.Wait() // 阻塞等待登记的goroutine完成
}

   协程的调度(GMP):

  • G:表示 goroutine,每执行一次go f()就创建一个 G,包含要执行的函数和上下文信息。

  • 全局队列(Global Queue):存放等待运行的 G。

  • P:表示 goroutine 执行所需的资源,最多有 GOMAXPROCS 个。

  • P 的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建 G 时,G 优先加入到 P 的本地队列,如果本地队列满了会批量移动部分 G 到全局队列。

  • M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,当 P 的本地队列为空时,M 也会尝试从全局队列或其他 P 的本地队列获取 G。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。

  • Goroutine 调度器和操作系统调度器是通过 M 结合起来的,每个 M 都代表了1个内核线程,操作系统调度器负责把内核线程分配到 CPU 的核上执行。

  • GOMAXPROCS :Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个 OS 线程来同时执行 Go 代码。默认值是机器上的 CPU 核心数。例如在一个 8 核心的机器上,GOMAXPROCS 默认为 8。Go语言中可以通过runtime.GOMAXPROCS函数设置当前程序并发时占用的 CPU逻辑核心数。(Go1.5版本之前,默认使用的是单核心执行。Go1.5 版本之后,默认使用全部的CPU 逻辑核心数。)

调度器步骤

  1. 创建与入队:G(协程)创建后,优先入 P 本地队列,若本地队列满,会放到全局队列。
  2. 调度准备:P 绑定 M(内核线程映射),M 从 P 本地队列取 G 执行;本地队列空时,会从全局队列或其他 P 偷取 G 补充。
  3. 执行与切换:M 执行 G,若 G 阻塞(如 I/O 操作),P 会解绑当前 M、关联新 M 继续调度其他 G;G 恢复后重新入队等待执行 。
  4. 资源协调:GOMAXPROCS 控制 P 数量,协调 CPU 核心(通过操作系统调度器)与 M 映射,让 G 高效利用计算资源,实现并发调度 。

二.通道(channel)

问题:

单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。虽然可以使用共享内存(通过调用函数)进行数据交换,但是共享内存在不同的 goroutine 中容易发生竞态问题。为了保证数据交换的正确性,很多并发模型中必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。

channel:

单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。从而引出通道实现协程之前的数据传输与共享

channel特点:

  1. Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序
  2. 每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
  3. 支持同步和数据共享,避免了显式的锁机制。
  4. 使用 chan 关键字创建,通过 <- 操作符发送和接收数据。
  5. 声明的通道类型变量需要使用内置的make函数初始化之后才能使用
make(chan 元素类型, [缓冲大小])//默认值为nil
var ch chan int
fmt.Println(ch) // <nil>ch4 := make(chan int)
ch5 := make(chan bool, 1)  // 声明一个缓冲区大小为1的通道x := <- ch // 从ch中接收值并赋值给变量x
<-ch       // 从ch中接收值,忽略结果<- chan int // 只接收通道,只能接收不能发送
chan <- int // 只发送通道,只能发送不能接收

缓存通道和无缓存通道的区别:

无缓存通道:无缓冲的通道只有在有接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的阶段。同理,如果对一个无缓冲通道执行接收操作时,没有任何向通道中发送值的操作那么也会导致接收操作阻塞

缓存通道:只要通道的容量大于零,那么该通道就属于有缓冲的通道,通道的容量表示通道中最大能存放的元素数量。当通道内已有元素数达到最大容量后,再向通道执行发送操作就会阻塞,除非有从通道执行接收操作。

注意事项:

  • 对一个关闭的通道再发送值就会导致 panic。
  • 对一个关闭的通道进行接收会一直获取值直到通道为空。
  • 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
  • 关闭一个已经关闭的通道会导致 panic。

三.锁

当多个 goroutine 同时操作一个资源(临界区)或者一个全局变量资源时的情况,这种情况下就会发生竞态问题(数据竞态)如多个协程同时对map进行存和删除

互斥锁:
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同一时间只有一个 goroutine 可以访问共享资源。Go 语言中使用sync包中提供的Mutex类型来实现互斥锁。

sync.Mutex提供了两个方法供我们使用。
func (m *Mutex) Lock()    获取互斥锁
func (m *Mutex) Unlock()    释放互斥锁

读写互斥锁
互斥锁是完全互斥的,但是实际上有很多场景是读多写少的,当我们并发的去读取一个资源而不涉及资源修改的时候是没有必要加互斥锁的,这种场景下使用读写锁是更好的一种选择。读写锁在 Go 语言中使用sync包中的RWMutex类型。

func (rw *RWMutex) Lock()    获取写锁
func (rw *RWMutex) Unlock()    释放写锁
func (rw *RWMutex) RLock()    获取读锁
func (rw *RWMutex) RUnlock()    释放读锁
func (rw *RWMutex) RLocker() Locker    返回一个实现Locker接口的读写锁

读写锁分为两种:读锁和写锁。当一个 goroutine 获取到读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁,如果是获取写锁就会等待;而当一个 goroutine 获取写锁之后,其他的 goroutine 无论是获取读锁还是写锁都会等待。

四.协程控制

1.通过多返回值,判断通道是否已经关闭

value, ok := <- ch

value:从通道中取出的值,如果通道被关闭则返回对应类型的零值。
ok:通道ch关闭时返回 false,否则返回 true。

2.for-range来循环遍历接收通道的值,当通道的值为空时,结束循环

func f3(ch chan int) {
for v := range ch {
fmt.Println(v)
}
}

通常我们会选择使用for range循环从通道中接收值,当通道被关闭后,会在通道内的所有值被接收完毕后会自动退出循环。

注意:ch通道在遍历到最后的数据的时候,如果通道没有关闭,导致 range 一直等待新的数据,而发送方已经发送完所有数据。所以发送方发送完数据必须关闭才能用range。

3.select多路复用

在某些场景下我们可能需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以被接收那么当前 goroutine 将会发生阻塞。你也许会写出如下代码尝试使用遍历的方式来实现从多个通道中接收值。

for{
// 尝试从ch1接收值
data, ok := <-ch1
// 尝试从ch2接收值
data, ok := <-ch2

}
这种方式虽然可以实现从多个通道接收值的需求,但是程序的运行性能会差很多。Go 语言内置了select关键字,使用它可以同时响应多个通道的操作。

Select 的使用方式类似于之前学到的 switch 语句,它也有一系列 case 分支和一个默认的分支。每个 case 分支会对应一个通道的通信(接收或发送)过程。select 会一直等待,直到其中的某个 case 的通信操作完成时,就会执行该 case 分支对应的语句。具体格式如下:

select {
case <-ch1:
//...
case data := <-ch2:
//...
case ch3 <- 10:
//...
default:
//默认操作
}

Select 语句具有以下特点:

  • 可处理一个或多个 channel 的发送/接收操作。
  • 如果多个 case 同时满足,select 会随机选择一个执行。
  • 对于没有 case 的 select 会一直阻塞,可用于阻塞 main 函数,防止退出。
  • 如果一个case中有多个通道发送数据,则接收时会串行排列等待被接受

4.通过一个新的通道数据来控制主协程等待分协程执行完在执行

cah1 := make(chan int, 100)cha2 := make(chan bool)go s(cah1, cha2)for k := 0; k < 100; k++ {cah1 <- k*2 - 1}close(cah1)<-cha2
}// 控制主协程等待分协程运行,可以用通道信号,当分协程中要执行的内容执行完
// 在向cha2发送信号,在主协程等待信号,这样的话就可以让主协程等待
func s(cah1 chan int, cha2 chan bool) {for v := range cah1 {fmt.Println(v)}cha2 <- false
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92563.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92563.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图机器学习(15)——链接预测在社交网络分析中的应用

图机器学习&#xff08;15&#xff09;——链接预测在社交网络分析中的应用0. 链接预测1. 数据处理2. 基于 node2vec 的链路预测3. 基于 GraphSAGE 的链接预测3.1 无特征方法3.2 引入节点特征4. 用于链接预测的手工特征5. 结果对比0. 链接预测 如今&#xff0c;社交媒体已成为…

每日一算:华为-批萨分配问题

题目描述"吃货"和"馋嘴"两人到披萨店点了一份铁盘&#xff08;圆形&#xff09;披萨&#xff0c;并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。但是粗心的服务员将披萨切成了每块大小都完全不同的奇数块&#xff0c;且肉眼能分辨出大小。由于两人都…

Transfusion,Show-o and Show-o2论文解读

目录 一、Transfusion 1、概述 2、方法 二、Show-o 1、概述 2、方法 3、训练 三、Show-o2 1、概述 2、模型架构 3、训练方法 4、实验 一、Transfusion 1、概述 Transfusion模型应该是Show系列&#xff0c;Emu系列的前传&#xff0c;首次将文本和图像生成统一到单…

聊聊 Flutter 在 iOS 真机 Debug 运行出现 Timed out *** to update 的问题

最近刚好有人在问&#xff0c;他的 Flutter 项目在升级之后出现 Error starting debug session in Xcode: Timed out waiting for CONFIGURATION_BUILD_DIR to update 问题&#xff0c;也就是真机 Debug 时始终运行不了的问题&#xff1a; 其实这已经是一个老问题了&#xff0c…

《R for Data Science (2e)》免费中文翻译 (第1章) --- Data visualization(2)

写在前面 本系列推文为《R for Data Science (2)》的中文翻译版本。所有内容都通过开源免费的方式上传至Github&#xff0c;欢迎大家参与贡献&#xff0c;详细信息见&#xff1a; Books-zh-cn 项目介绍&#xff1a; Books-zh-cn&#xff1a;开源免费的中文书籍社区 r4ds-zh-cn …

【机器学习【9】】评估算法:数据集划分与算法泛化能力评估

文章目录一、 数据集划分&#xff1a;训练集与评估集二、 K 折交叉验证&#xff1a;提升评估可靠性1. 基本原理1.1. K折交叉验证基本原理1.2. 逻辑回归算法与L22. 基于K折交叉验证L2算法三、弃一交叉验证&#xff08;Leave-One-Out&#xff09;1、基本原理2、代码实现四、Shuff…

CodeBuddy三大利器:Craft智能体、MCP协议和DeepSeek V3,编程效率提升的秘诀:我的CodeBuddy升级体验之旅(个性化推荐微服务系统)

&#x1f31f; 嗨&#xff0c;我是Lethehong&#xff01;&#x1f31f; &#x1f30d; 立志在坚不欲说&#xff0c;成功在久不在速&#x1f30d; &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞⬆️留言收藏&#x1f680; &#x1f340;欢迎使用&#xff1a;小智初学计…

Spring Boot 整合 Redis 实现发布/订阅(含ACK机制 - 事件驱动方案)

Spring Boot整合Redis实现发布/订阅&#xff08;含ACK机制&#xff09;全流程一、整体架构二、实现步骤步骤1&#xff1a;添加Maven依赖<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…

Sklearn 机器学习 线性回归

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Sklearn 机器学习线性回归实战详解 线性回归是机器学习中最基础也最经典的算法之一,…

AJAX案例合集

案例一&#xff1a;更换网站背景JS核心代码<script>document.querySelector(.bg-ipt).addEventListener(change, e > {//选择图片上传&#xff0c;设置body背景const fd new FormData()fd.append(img, e.target.files[0])axios({url: http://hmajax.itheima.net/api/…

vscode环境下c++的常用快捷键和插件

本文提供一些能够在vscode的环境下&#xff0c;提高c代码书写效率的快捷键&#xff0c;插件以及设置等等。 快捷键ctrlshiftx&#xff1a; 弹出插件菜单ctrlshiftp&#xff1a;弹出命令面板可以快捷执行一些常见命令插件安装这个后&#xff0c;可以按住ctrl跳转到方法的实现&am…

React + ts 中应用 Web Work 中集成 WebSocket

一、Web Work定义useEffect(() > {let webSocketIndex -1const websocketWorker new Worker(new URL(./websocketWorker.worker.ts?worker, import.meta.url),{type: module // 必须声明模块类型});//初始化WEBSOCKET&#xff08;多个服务器选择最快建立连接…

RabbitMQ面试精讲 Day 3:Exchange类型与路由策略详解

【RabbitMQ面试精讲 Day 3】Exchange类型与路由策略详解 文章标签 RabbitMQ,消息队列,Exchange,路由策略,AMQP,面试题,分布式系统 文章简述 本文是"RabbitMQ面试精讲"系列第3天内容&#xff0c;深入解析RabbitMQ的核心组件——Exchange及其路由策略。文章详细剖析…

深入解析Hadoop MapReduce Shuffle过程:从环形缓冲区溢写到Sort与Merge源码

MapReduce与Shuffle过程概述在大数据处理的经典范式MapReduce中&#xff0c;Shuffle过程如同人体血液循环系统般连接着计算框架的各个组件。作为Hadoop最核心的分布式计算模型&#xff0c;MapReduce通过"分而治之"的思想将海量数据处理分解为Map和Reduce两个阶段&…

Kafka MQ 消费者

Kafka MQ 消费者 1 创建消费者 在读取消息之前,需要先创建一个KafkaConsumer对象。创建KafkaConsumer对象与创建KafkaProducer对象非常相似—把想要传给消费者的属性放在Properties对象里。本章后续部分将深入介绍所有的配置属性。为简单起见,这里只提供3个必要的属性:boo…

人工智能——Opencv图像色彩空间转换、灰度实验、图像二值化处理、仿射变化

一、图像色彩空间转换&#xff08;一&#xff09;颜色加法1、直接相加1、直接相加2、调用cv.add()函数进行饱和操作 在OpenCV中进行颜色的加法&#xff0c;我们说图像即数组&#xff0c;所以从数据类型来说我们可以直接用numpy的知识来进行直接相加&#xff0c;但是存在…

【JToken】JToken == null 判断无效的问题

if (innerNode null) {continue; }Debug.Log($"toNode type: {node["toNode"]?.GetType()}");发现这个JToken 无法正确的判断 是否为 null&#xff0c;再排除逻辑问题后&#xff0c;我基本能确定的是 这个对象 不返回的不是真正的C# NULL 输出类型后是 N…

C++基于libmodbus库实现modbus TCP/RTU通信

今天看到了一个参考项目中用到了modbus库&#xff0c;看着使用很是方便&#xff0c;于是记录一下。后面有时间了或者用到了再详细整理。 参考&#xff1a;基于libmodbus库实现modbus TCP/RTU通信-CSDN博客 一、介绍 1.1库文件包含 1.2最简单的使用 本人在QT6.5下&#xff0…

【原创】微信小程序添加TDesign组件

前言 TDesign 是腾讯公司推出的一款UI界面库,至于腾讯的实力嘛,也不用多说了。 官网:https://tdesign.tencent.com/ 源码:https://github.com/Tencent/tdesign 目前处于活跃状态,发文前5日,该库仍在更新中… 遇到的问题 虽然腾讯为微信小程序开发提供了一个讨论的论坛,…

Vue的路由模式的区别和原理

路由模式 Vue 的路由模式指的是 Vue Router 提供的 URL 处理方式&#xff0c;主要有两种&#xff1a;Hash 模式和History 模式。 Hash模式 在 Vue Router 中&#xff0c;默认使用的是 hash 模式&#xff0c;即 mode: hash。如果想要使用 history 模式&#xff0c;可以设置 mode…