Go八股(Ⅵ)Goroutine 以及其中的锁和思想
Goroutine与并发编程的关系
什么是并发
是指多个任务在同一时间段内进行处理,但不一定是在同一时刻执行。并发强调的是“结构上的并行性”,也就是说,程序能够在一个时间端内同时处理多个任务,但是这些任务可能是交替进行的。例如:一个cpu可以迅速切换任务的上下文,给人一种多个任务在“并行”执行的感觉。
并发的主要特点是能够处理多个任务,而这些任务并不需要同时执行,但在某种情况下它们可以并行执行。并发的关键在于如何有效的切换任务和处理共享资源。
并发的核心就是让多个任务有机会被处理,而不需要等待其他任务完全结束。
goroutine与并发的关系
在Go语言中,goruoutine是并发的基本单位,它是一种轻量级的线程,由Go运行时管理。与传统的线程相比,goroutine占用的资源非常小,因此在程序中启动成千上万个goroutine,不会造成过高的开销。
goroutine通过Go的调度器进行管理,调度器负责讲goroutine映射到操作系统的线程上。goroutine在并发模型中的作用是允许程序处理多个任务,它的本质是轻量级的“线程”,但由于Go提供了对goroutine的高效调度,goroutine的使用非常灵活和高效。
goroutine 如何实现并发?
- 轻量级任务:goroutine 被设计为非常轻量级的任务。启动一个 goroutine 需要的内存比传统线程要少很多,因此你可以并发执行成千上万的 goroutine。
- 自动调度:Go 运行时内置的调度器会自动将 goroutine 映射到 CPU 上的可用线程上,从而实现并发执行。Go 程序不需要手动管理线程池或调度机制。
- 非阻塞执行:在执行 goroutine 时,即使某个 goroutine 因为 I/O 操作或其他原因阻塞了,Go 的调度器仍然可以将其他 goroutine 调度到 CPU 上继续执行,从而保持并发运行。
Go语言中的锁
互斥锁,读写锁
锁的思想:乐观锁,悲观锁
互斥锁()
1.1定义
互斥锁是一个用于保护临界区的同步用语,它的所用是确保统一时刻只有一个goroutine能访问被保护的共享资源。通过互斥锁,可以避免并发访问带来的数据竞争问题。
1.2示例代码:
package mainimport ("fmt""sync"
)var (mutex sync.Mutexcount int
)func increment() {mutex.Lock() // 获取锁count++mutex.Unlock() // 释放锁
}func main() {var wg sync.WaitGroup// 启动多个 goroutine 并发执行for i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Done()increment()}()}wg.Wait() // 等待所有 goroutine 完成fmt.Println("Final count:", count)
}
在这个示例中,mutex
被用来确保同一时刻只有一个 goroutine 能够访问 count
变量。由于有多个 goroutine 在并发地执行 increment
函数,通过 mutex.Lock()
和 mutex.Unlock()
确保了线程安全。
1.3注意事项
- 死锁:如果一个 goroutine 在持有锁时没有及时释放锁,或者两个 goroutine 相互等待对方释放锁,可能会导致死锁。为了避免死锁,确保每个
Lock()
都有对应的Unlock()
,并且尽量减少锁持有的时间。 - 锁的嵌套:避免在持有锁时再次请求锁,除非非常必要,否则会增加死锁的风险。
读写锁
2.1 定义与原理
读写锁(RWMutex)是为了优化读操作频繁的场景设计的锁。与互斥锁不同,读写锁允许多个 goroutine 同时进行读操作,但写操作是互斥的,只有在没有任何读操作或写操作进行时,才能进行写操作。
- RLock():用于加读锁,多个 goroutine 可以同时持有读锁,只要没有其他 goroutine 持有写锁。
- RUnlock():释放读锁。
- Lock():用于加写锁,写锁是独占的,只有一个 goroutine 可以持有写锁。
- Unlock():释放写锁。
2.2 使用示例
package mainimport ("fmt""sync" )var (rwMutex sync.RWMutexdata int )func read() {rwMutex.RLock() // 获取读锁defer rwMutex.RUnlock()fmt.Println("Reading data:", data) }func write(value int) {rwMutex.Lock() // 获取写锁defer rwMutex.Unlock()data = valuefmt.Println("Writing data:", value) }func main() {var wg sync.WaitGroup// 启动多个 goroutine 执行读操作for i := 0; i < 5; i++ {wg.Add(1)go func() {defer wg.Done()read()}()}// 启动一个 goroutine 执行写操作wg.Add(1)go func() {defer wg.Done()write(42)}()wg.Wait() }
在这个示例中,
read
函数通过rwMutex.RLock()
获取读锁,允许多个 goroutine 同时执行读取操作。而write
函数则通过rwMutex.Lock()
获取写锁,保证写操作是独占的,不会与读操作同时进行。
2.3 适用场景
- 读多写少:读写锁特别适合读多写少的场景。当读操作远远多于写操作时,使用读写锁可以显著提高性能,因为多个读操作可以并发执行,而不必等待锁的释放。
- 写操作必须独占:写操作会阻塞所有读操作和其他写操作,确保写操作的安全性。
2.4 与互斥锁的区别
- 互斥锁是严格的互斥机制,即同一时刻只能有一个 goroutine 执行临界区代码(无论是读还是写)。
- 读写锁允许多个 goroutine 同时读共享资源,但在写共享资源时,写操作需要独占资源。
当程序中存在大量读操作时,读写锁可以提供更高的并发性,因为它允许多个读操作同时进行,而不会像互斥锁那样相互阻塞。
3. 何时使用互斥锁,何时使用读写锁?
- 使用互斥锁:
- 如果操作是读写混合的,或者读写的比例相当,使用互斥锁会更简单直接。
- 如果共享资源的访问量很小,读写锁可能带来的复杂性不值得使用。
- 使用读写锁:
- 如果你的应用场景下,读操作远多于写操作,使用读写锁可以提高性能,允许多个 goroutine 并发读数据,同时保证写操作的独占性。
- 读写锁比互斥锁更复杂,因此如果读写比不明显,使用互斥锁可能更为简单
两种锁的思想:悲观锁和乐观锁
悲观锁
2.1 定义与原理
悲观锁的核心思想是假设并发操作会引发冲突,因此在访问共享资源时,采用加锁的方式防止其他 goroutine 同时访问该资源。只有在获取锁之后,才能对共享资源进行操作,其他 goroutine 必须等待锁被释放。
- 特点:总是先加锁,再访问资源,保证在同一时刻只有一个 goroutine 能够访问资源。
- 锁的粒度:锁的粒度较大(例如,一次获取整个资源的锁),因此会影响系统的并发性能。
在 Go 中,sync.Mutex
和 sync.RWMutex
就是典型的悲观锁实现:
sync.Mutex
:互斥锁,整个资源加锁,防止其他 goroutine 访问。sync.RWMutex
:读写锁,写操作是互斥的,而读操作可以并发执行,但在写操作时,所有读操作和其他写操作会被阻塞。
2.2 示例
假设我们有一个共享变量,需要通过悲观锁来保护:
package mainimport ("fmt""sync"
)var (mutex sync.Mutexcount int
)func increment() {mutex.Lock() // 获取锁defer mutex.Unlock()count++
}func main() {var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Done()increment()}()}wg.Wait()fmt.Println("Final count:", count)
}
在这个示例中,mutex.Lock()
和 mutex.Unlock()
确保了只有一个 goroutine 能访问 count
变量,这就是一种典型的悲观锁机制。
2.3 使用场景
悲观锁通常用于以下场景:
- 资源竞争严重:当多个 goroutine 频繁争抢资源时,悲观锁能确保对资源的访问是互斥的,避免并发问题。
- 数据一致性要求高:当你无法接受并发操作对数据产生不一致时,使用悲观锁可以避免这类问题。
然而,悲观锁可能导致性能瓶颈,因为锁住了共享资源的访问,其他 goroutine 必须等待锁释放,导致并发性降低。
乐观锁
3.1 定义与原理
与悲观锁相对,乐观锁的核心思想是假设并发操作不会引发冲突,因此它不主动加锁,而是乐观地认为不会有并发冲突。在操作共享资源之前,乐观锁并不会加锁,而是在操作后检测是否发生了冲突。如果冲突发生,则回滚操作,重试或者放弃。
乐观锁通常通过一些“版本控制”机制来实现,例如通过“版本号”或者“时间戳”来判断数据是否发生变化。
- 特点:乐观锁不会阻塞其他 goroutine,只有在数据写入时才检测冲突。通常适用于读多写少的场景。
- 回滚重试机制:如果在提交操作时发现冲突,乐观锁通常会选择回滚并重试。
3.2 示例
Go 本身并没有直接实现乐观锁,但你可以通过一些简单的机制来模拟乐观锁。一个常见的方法是通过版本号(或时间戳)来判断资源是否被修改。
package mainimport ("fmt""sync"
)type Counter struct {value intversion int // 版本号,用于乐观锁mu sync.Mutex
}func (c *Counter) increment() bool {c.mu.Lock()defer c.mu.Unlock()// 记录当前版本号currentVersion := c.version// 执行增量操作c.value++// 模拟乐观锁:如果版本号没有变化,认为没有并发冲突if c.version == currentVersion {c.version++return true // 操作成功}// 如果版本号发生变化,表示有其他操作修改了数据,返回失败return false
}func main() {var wg sync.WaitGroupcounter := &Counter{}for i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Done()// 模拟乐观锁重试for !counter.increment() {fmt.Println("Retrying...")}}()}wg.Wait()fmt.Println("Final count:", counter.value)
}
3.3 使用场景
乐观锁适用于以下场景:
- 读操作远多于写操作:在大多数情况下,数据不会发生冲突,乐观锁能够减少不必要的加锁,提高性能。
- 冲突发生概率低:如果你知道并发冲突发生的概率非常低,使用乐观锁可以提高效率。
- 需要高并发读操作:乐观锁不需要加锁,允许多个 goroutine 同时读取数据,适用于高并发读场景。
然而,乐观锁的缺点在于需要处理冲突时的回滚和重试,尤其是在高并发写操作的场景中,重试的开销可能非常大。
乐观锁与悲观锁的对比
特性 | 悲观锁 | 乐观锁 |
---|---|---|
假设 | 假设并发冲突会发生,采取措施避免冲突 | 假设并发冲突不会发生,事后检查冲突 |
加锁方式 | 总是加锁,互斥访问 | 不加锁,事后检查冲突 |
性能 | 可能影响并发性能,锁竞争时会阻塞 | 高并发读取时性能较好,但有回滚重试开销 |
适用场景 | 资源竞争严重,数据一致性要求高 | 读多写少,冲突概率低的场景 |
总结
- 悲观锁:通过加锁保证数据一致性,适用于资源竞争严重的场景,但会影响系统的并发性能。
- 乐观锁:假设没有冲突,允许多个 goroutine 并发访问资源,适用于读多写少、冲突概率低的场景,通常需要进行冲突检测和回滚。
在 Go 语言中,虽然我们大多数情况下使用的是悲观锁(例如 sync.Mutex
和 sync.RWMutex
),但在某些场景下,理解并应用乐观锁的思想可以帮助我们提升并发性能,尤其是在读操作远多于写操作的情况下。
相关文章:
Go八股(Ⅵ)Goroutine 以及其中的锁和思想
Goroutine与并发编程的关系 什么是并发 是指多个任务在同一时间段内进行处理,但不一定是在同一时刻执行。并发强调的是“结构上的并行性”,也就是说,程序能够在一个时间端内同时处理多个任务,但是这些任务可能是交替进行的。例如…...

向潜在安全信息和事件管理 SIEM 提供商提出的六个问题
收集和解读数据洞察以制定可用的解决方案是强大网络安全策略的基础。然而,组织正淹没在数据中,这使得这项任务变得复杂。 传统的安全信息和事件管理 ( SIEM ) 工具是组织尝试使用的一种方法,但由于成本、资源和可扩展性等几个原因࿰…...
蓝桥杯每日真题 - 第15天
题目:(钟表) 题目描述(13届 C&C B组B题) 解题思路: 理解钟表指针的运动: 秒针每分钟转一圈,即每秒转6度。 分针每小时转一圈,即每分钟转6度。 时针每12小时转一圈…...
Python的Matplotlib
介绍: Matplotlib 是一个非常强大的 Python 绘图库,支持多种不同类型的图表。以下是 Matplotlib 支持的一些常见图表类型: 前情提要: from matplotlib import rcParams# 设置支持中文的字体 rcParams[font.sans-serif] [SimHei…...
Python数据分析:分组转换transform方法
大家好,在数据分析中,需要对数据进行分组统计与计算,Pandas的groupby功能提供了强大的分组功能。transform方法是groupby中常用的转换方法之一,它允许在分组的基础上进行灵活的转换和计算,并将结果与原始数据保持相同的…...
高效灵活的Django URL配置与反向URL实现方案
高效灵活的Django URL配置与反向URL实现方案 目录 📑 1. 基本的Django URL配置及反向URL的实现 🔧 2. 使用path()替代re_path()配置URL的优势与劣势 🛠️ 3. 使用URL命名空间(namespace)提高URL管理的可维护性 &…...
深入探讨 MySQL 配置与优化:从零到生产环境的最佳实践20241112
深入探讨 MySQL 配置与优化:从零到生产环境的最佳实践 引言 MySQL 是全球最受欢迎的开源关系型数据库之一,其高性能、灵活性和广泛的社区支持使其成为无数开发者的首选。然而,部署一台高效、稳定的 MySQL 实例并非易事。本文将结合一个实际…...
Java-Redisson分布式锁+自定义注解+AOP的方式来实现后台防止重复请求扩展
1. 添加依赖 首先,在项目的pom.xml文件中添加Redisson和Spring AOP的相关依赖: <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.8</version> </dependency> <dependency…...

Java 全栈知识体系
包含: Java 基础, Java 部分源码, JVM, Spring, Spring Boot, Spring Cloud, 数据库原理, MySQL, ElasticSearch, MongoDB, Docker, k8s, CI&CD, Linux, DevOps, 分布式, 中间件, 开发工具, Git, IDE, 源码阅读,读书笔记, 开源项目......

树状数组+概率论,ABC380G - Another Shuffle Window
目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 G - Another Shuffle Window 二、解题报告 1、思路分析 不难用树状数组计…...
机器学习day1-数据集
机器学习 一、机器学习 1.定义 让计算机在数据中学习规律并根据得到的规律对未来进行预测。 2.发展史 19世纪50年代:图灵测试提出、塞缪尔开发的西洋跳棋程序,标志着机器学习正式进入发展期 19世纪80年代:神经网络反向传播(…...
【Golang】——Gin 框架中的路由与请求处理
文章目录 1. 路由基础1.1 什么是路由?1.2 Gin 中的路由概述 2. 创建简单路由2.1 基本路由定义2.2 不同请求方法的路由 3. 路由参数3.1 路径参数3.2 查询参数 4. 路由分组4.1 为什么使用路由分组?4.2 路由分组示例 5. 请求处理与响应5.1 Gin 中的 Context…...
nuxt3添加wowjs动效
1、安装wowjs pnpm i wowjs1.1.32、node_modules复制wowjs代码 路径/node_modules/wowjs/dist/wow.js。不知道路径则查看node_modules/wowjs/package.json里面的main选项 2.1、在public文件夹创建wowjs.js文件 /public/wowjs.js export default (callthis) > { // !!// 这是…...

我们是如何实现 TiDB Cloud Serverless 的 - 成本篇
作者: shiyuhang0 原文来源: https://tidb.net/blog/fbedeea4 背景 Serverless 数据库是云原生时代的产物,它提供全托管,按需付费,自动弹性的云数据库服务,让客户免于繁重的数据库运维工作。关于 Serve…...
PCL算法汇总
参考 【2024最新版】PCL点云处理算法汇总(C长期更新版)_pcl点云聚类c-CSDN博客...

sql注入之二次注入(sqlilabs-less24)
二阶注入(Second-Order Injection)是一种特殊的 SQL 注入攻击,通常发生在用户输入的数据首先被存储在数据库中,然后在后续的操作中被使用时,触发了注入漏洞。与传统的 SQL 注入(直接注入)不同&a…...
Android compose 软键盘 遮挡对话框中TextField 输入框
在AlertDialog对话框中含有TextField输入框时,弹出软件盘会遮挡输入框 解决1: 在AndroidManifest.xml的 MainActivity中添加如下 android:windowSoftInputMode"adjustResize" 然后AlertDialog 中的modify. modify.windowInsetsP…...

spring-data-elasticsearch 3.2.4 实现桶bucket排序去重,实现指定字段的聚合搜索
一、背景 es索引有一个文档CourseIndex,下面是示意: creatorIdgradesubjectnameno1002270英语听力课程一N00232DS91004380数学口算课程N00209DK71003480物理竞赛课程N00642XS21002280英语听力课程二N00432WS31002290英语听力课程三N002312DP5 在搜索的时候&#…...
【项目开发】分析六种常用软件架构
未经许可,不得转载。 文章目录 软件架构核心内容设计原则分层架构常见层次划分优缺点应用场景事件驱动架构核心组件优缺点应用场景微核架构核心概念优缺点应用场景微服务架构核心组件设计与实施优缺点应用场景云架构云架构模式优缺点应用场景软件架构 软件架构是指一个软件系…...
算法和程序的区别
算法(Algorithm)和程序(Program)是计算机科学中两个密切相关但不同的概念。让我们通过以下几个方面来比较它们: ### 1. 设计 vs 实现 - **算法设计(Algorithm Design)**: - **定…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...