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

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Windows配置域名映射IP

一、找到 hosts 文件 打开 C:\Windows\System32\drivers\etc 二、添加hosts文件修改、写入权限 右击hosts文件&#xff0c;点击属性 -> 安全 -> Users -> 编辑 -> Users -> 添加修改、写入权限 -> 确定 -> 确定 进入常规&#xff0c;将只读属性关闭 三、…

专题二十_动态规划_简单多状态dp问题_买卖股票系列问题_算法专题详细总结

目录 动态规划 1. 按摩师&#xff08;easy&#xff09; 解析&#xff1a; 1.状态表达式&#xff1a; 2.状态转移方程 3.初始化 4.填表方向 5.返回值&#xff1a; 代码编写&#xff1a; 总结&#xff1a; 2. 打家劫舍II &#xff08;medium&#xff09; 解析&#xf…

多模态简述

多模态学习概念 【多模态简述-哔哩哔哩】 https://b23.tv/UrUyfln 定义&#xff1a; 模态&#xff1a;事物表达或感知的方式 多模态&#xff1a;研究异构和相互连接数据的科学&#xff0c;涵盖了从原始的器官信号到抽象概念的多种模态 语音和语言是理解人物交互的关键模态&am…

mac2019环境 Airflow+hive+spark+hadoop本地环境安装

1 环境介绍 本地安装可分为两个部分&#xff0c;mac软件环境&#xff0c; python开发环境 ps: 安装过程参考chatgpt、csdn文章 1.1 mac软件环境 目标安装的的软件是hive、apache-spark、hadoop&#xff0c;但是这三个软件又依赖java(spark依赖&#xff09;、ssh&#xff08…

HarmonyOS4+NEXT星河版入门与项目实战--------开发工具与环境准备

文章目录 1、熟悉鸿蒙官网1、打开官网2、下载 DevEco Studio3、HarmonyOS 资源库4、开发指南与API 2、安装 DevEco Studio1、软件安装2、配置开发工具 1、熟悉鸿蒙官网 1、打开官网 百度搜索 鸿蒙开发者官网 点击进入开发者官网&#xff0c;点击开发&#xff0c;可以看到各种…

11.16 JavaScript

什么是JavaScript&#xff1f; JavaScript&#xff08;简称&#xff1a;js&#xff09;是一门跨平台&#xff0c;面向对象的脚本语言&#xff0c;是用来控制网页行为的&#xff0c;它能使网页可交互。JavaScript和java是完全不同的语言&#xff0c;不论是概念还是设计。但是基…

【网络安全面经】技术性问题

1.SQL注入原理 主要基于Web应用程序对用户输入数据的合法性缺乏严格的判断或过滤 2.windows上提权的方式和linux提权方式 windows&#xff1a;本地溢出漏洞提权&#xff0c;AT(计划任务提权)&#xff0c;SC(创建服务提权)&#xff0c;PS(微软官方工具pstool)&#xff0c;数据…

20241116下载中科创达的TurboX D660核心板的Android11的SDK的详细LOG

20241116下载中科创达的TurboX D660核心板的Android11的SDK的详细LOG 2024/11/16 15:28 下载速度&#xff0c;工作日&#xff1a;20MBps/周末30MBps。 【实际情况&#xff0c;取决于您的实际网络环境】 https://docs.thundercomm.com/turbox_doc/products/smart-modules/turbox…

计算机网络 (6)物理层的基本概念

前言 计算机网络物理层是OSI模型&#xff08;开放式系统互联模型&#xff09;中的第一层&#xff0c;也是七层中的最底层&#xff0c;它涉及到计算机网络中数据的物理传输。 一、物理层的主要任务和功能 物理层的主要任务是处理物理传输介质上的原始比特流&#xff0c;确保数据…

大模型(LLMs)微调篇

大模型&#xff08;LLMs&#xff09;微调篇 一、如果想要在某个模型基础上做全参数微调&#xff0c;究竟需要多少显存&#xff1f; 一般 n B的模型&#xff0c;最低需要 16-20 n G的显存。&#xff08;cpu offload基本不开的情况下&#xff09; 二、为什么SFT之后感觉LLM傻了…

企业网络链路聚合、数据抓包、远程连接访问实验

前言&#xff1a; 随着信息技术的飞速发展和企业业务的不断扩大&#xff0c;企业网络面临着越来越多的挑战。其中&#xff0c;网络带宽、数据安全和远程访问等问题尤为突出。为了解决这些问题&#xff0c;我们进行了本次企业网络链路聚合、数据抓包和远程连接访问的实验。 链路…

移除元素(leetcode 27)

给定一个数组&#xff0c;在数组中删除等于这个目标值的元素&#xff0c;然后返回新数组的大小 数组理论&#xff1a; 数组是一个连续的类型相近的元素的一个集合&#xff0c;数组上的删除是覆盖&#xff0c;只能由后面的元素进行覆盖&#xff0c;而不能进行真正意义上的地理位…

前端面试笔试(三)

目录 一、数据结构算法等综合篇 二、代码输出篇 1.yield与生成器函数 2.this指向有关 3.instanceof 与Array.isArray 4.继承class cls extends Array&#xff0c;调用里面的sum方法 三、css、html、JavaScript篇 1.哪项不能提高dom元素操作效率&#xff1f; 2.contente…

7.高可用集群架构Keepalived双主热备原理

一. 高可用集群架构Keepalived双主热备原理 (1)主机+备机keepalived配置(192.168.1.171) ! Configuration File for keepalivedglobal_defs {# 路由id:当前安装keepalived节点主机的标识符,全局唯一router_id keep_101 } #计算机节点(主机配置) vrrp_instance VI_1 {</

IntelliJ IDEA 2023.2x——图文配置

IntelliJ IDEA 2023.2——配置说明 界面如下图所示 : 绿泡泡查找 “码猿趣事” 查找【idea99】 IntelliJ IDEA 的官方下载地址 IntelliJ IDEA 官网下载地址 一路上NEXT 到结尾&#xff1a; 继续NEXT 下一步:

Linux网络:守护进程

Linux网络&#xff1a;守护进程 会话进程组会话终端 守护进程setsiddaemon 在创建一个网络服务后&#xff0c;往往这个服务进程是一直运行的。但是对于大部分进程来说&#xff0c;如果退出终端&#xff0c;这个终端上创建的所有进程都会退出&#xff0c;这就导致进程的生命周期…

Linux Android 正点原子RK3568替换开机Logo完整教程

0.这CSDN是有BUG吗?大家注意:表示路径的2个点号全都变成3个点号啦! 接下来的后文中,应该是2个点都被CSDN变成了3个点: 1.将这两个 bmp 图片文件720x1280_8bit拷贝到内核源码目录下,替换内核源码中默认的 logo 图片。注意:此时还缺少电量显示图片 2.编译内核 make d…

安卓开发作业

整体效果: 安卓小作业 [TOC](页面配置) 整体框架有4个fragment页面,聊天,朋友,发现,设置. 配置如下: bash <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android" xm…

2024-ISCTF WP

Web 25时晓山瑞希生日会 经典 HTTP 头伪造&#xff0c;伪造流程如下&#xff1a; User-Agent: Project Sekai //伪造UA头 X-Forwarded-For:127.0.0.1 //伪造本地用户 伪造日期是本题最大的坑点&#xff0c;一直在想怎么伪造 25 时&#xff0c;没想到是二刺螈 搜索得知 …

VSCode+ESP-IDF开发ESP32-S3-DevKitC-1(1)开发环境搭建

VSCodeESP-IDF开发ESP32-S3-DevKitC-1&#xff08;1&#xff09;开发环境搭建 1.开发环境搭建&#xff08;安装ESP-IDF&#xff09;2.开发环境搭建&#xff08;安装VS Code&#xff09;3.开发环境搭建&#xff08;VSCode中安装ESP-IDF插件及配置&#xff09; 1.开发环境搭建&am…