当前位置: 首页 > news >正文

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.Mutexsync.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.Mutexsync.RWMutex),但在某些场景下,理解并应用乐观锁的思想可以帮助我们提升并发性能,尤其是在读操作远多于写操作的情况下。

相关文章:

Go八股(Ⅵ)Goroutine 以及其中的锁和思想

Goroutine与并发编程的关系 什么是并发 是指多个任务在同一时间段内进行处理&#xff0c;但不一定是在同一时刻执行。并发强调的是“结构上的并行性”&#xff0c;也就是说&#xff0c;程序能够在一个时间端内同时处理多个任务&#xff0c;但是这些任务可能是交替进行的。例如…...

向潜在安全信息和事件管理 SIEM 提供商提出的六个问题

收集和解读数据洞察以制定可用的解决方案是强大网络安全策略的基础。然而&#xff0c;组织正淹没在数据中&#xff0c;这使得这项任务变得复杂。 传统的安全信息和事件管理 ( SIEM ) 工具是组织尝试使用的一种方法&#xff0c;但由于成本、资源和可扩展性等几个原因&#xff0…...

蓝桥杯每日真题 - 第15天

题目&#xff1a;&#xff08;钟表&#xff09; 题目描述&#xff08;13届 C&C B组B题&#xff09; 解题思路&#xff1a; 理解钟表指针的运动&#xff1a; 秒针每分钟转一圈&#xff0c;即每秒转6度。 分针每小时转一圈&#xff0c;即每分钟转6度。 时针每12小时转一圈…...

Python的Matplotlib

介绍&#xff1a; Matplotlib 是一个非常强大的 Python 绘图库&#xff0c;支持多种不同类型的图表。以下是 Matplotlib 支持的一些常见图表类型&#xff1a; 前情提要&#xff1a; from matplotlib import rcParams# 设置支持中文的字体 rcParams[font.sans-serif] [SimHei…...

Python数据分析:分组转换transform方法

大家好&#xff0c;在数据分析中&#xff0c;需要对数据进行分组统计与计算&#xff0c;Pandas的groupby功能提供了强大的分组功能。transform方法是groupby中常用的转换方法之一&#xff0c;它允许在分组的基础上进行灵活的转换和计算&#xff0c;并将结果与原始数据保持相同的…...

高效灵活的Django URL配置与反向URL实现方案

高效灵活的Django URL配置与反向URL实现方案 目录 &#x1f4d1; 1. 基本的Django URL配置及反向URL的实现 &#x1f527; 2. 使用path()替代re_path()配置URL的优势与劣势 &#x1f6e0;️ 3. 使用URL命名空间&#xff08;namespace&#xff09;提高URL管理的可维护性 &…...

深入探讨 MySQL 配置与优化:从零到生产环境的最佳实践20241112

深入探讨 MySQL 配置与优化&#xff1a;从零到生产环境的最佳实践 引言 MySQL 是全球最受欢迎的开源关系型数据库之一&#xff0c;其高性能、灵活性和广泛的社区支持使其成为无数开发者的首选。然而&#xff0c;部署一台高效、稳定的 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, 源码阅读&#xff0c;读书笔记, 开源项目......

树状数组+概率论,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年代&#xff1a;图灵测试提出、塞缪尔开发的西洋跳棋程序&#xff0c;标志着机器学习正式进入发展期 19世纪80年代&#xff1a;神经网络反向传播&#xff08;…...

【Golang】——Gin 框架中的路由与请求处理

文章目录 1. 路由基础1.1 什么是路由&#xff1f;1.2 Gin 中的路由概述 2. 创建简单路由2.1 基本路由定义2.2 不同请求方法的路由 3. 路由参数3.1 路径参数3.2 查询参数 4. 路由分组4.1 为什么使用路由分组&#xff1f;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 的 - 成本篇

作者&#xff1a; shiyuhang0 原文来源&#xff1a; https://tidb.net/blog/fbedeea4 背景 Serverless 数据库是云原生时代的产物&#xff0c;它提供全托管&#xff0c;按需付费&#xff0c;自动弹性的云数据库服务&#xff0c;让客户免于繁重的数据库运维工作。关于 Serve…...

PCL算法汇总

参考 【2024最新版】PCL点云处理算法汇总&#xff08;C长期更新版&#xff09;_pcl点云聚类c-CSDN博客...

sql注入之二次注入(sqlilabs-less24)

二阶注入&#xff08;Second-Order Injection&#xff09;是一种特殊的 SQL 注入攻击&#xff0c;通常发生在用户输入的数据首先被存储在数据库中&#xff0c;然后在后续的操作中被使用时&#xff0c;触发了注入漏洞。与传统的 SQL 注入&#xff08;直接注入&#xff09;不同&a…...

Android compose 软键盘 遮挡对话框中TextField 输入框

在AlertDialog对话框中含有TextField输入框时&#xff0c;弹出软件盘会遮挡输入框 解决1&#xff1a; 在AndroidManifest.xml的 MainActivity中添加如下 android:windowSoftInputMode"adjustResize" 然后AlertDialog 中的modify. modify.windowInsetsP…...

spring-data-elasticsearch 3.2.4 实现桶bucket排序去重,实现指定字段的聚合搜索

一、背景 es索引有一个文档CourseIndex&#xff0c;下面是示意: creatorIdgradesubjectnameno1002270英语听力课程一N00232DS91004380数学口算课程N00209DK71003480物理竞赛课程N00642XS21002280英语听力课程二N00432WS31002290英语听力课程三N002312DP5 在搜索的时候&#…...

【项目开发】分析六种常用软件架构

未经许可,不得转载。 文章目录 软件架构核心内容设计原则分层架构常见层次划分优缺点应用场景事件驱动架构核心组件优缺点应用场景微核架构核心概念优缺点应用场景微服务架构核心组件设计与实施优缺点应用场景云架构云架构模式优缺点应用场景软件架构 软件架构是指一个软件系…...

算法和程序的区别

算法&#xff08;Algorithm&#xff09;和程序&#xff08;Program&#xff09;是计算机科学中两个密切相关但不同的概念。让我们通过以下几个方面来比较它们&#xff1a; ### 1. 设计 vs 实现 - **算法设计&#xff08;Algorithm Design&#xff09;**&#xff1a; - **定…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...