Go中为什么不建议用锁?

在这里插入图片描述

Go语言中是不建议用锁,而是用通道Channel来代替(不要通过共享内存来通信,而通过通信来共享内存),当然锁也是可以用,锁是防止同一时刻多个goroutine操作同一个资源;

GO语言中,要传递某个数据给另一个goroutine(协程),可以把这个数据封装成一个对象,然后把这个对象的指针传入某个channel中,另外一个goroutine从这个channel中读出这个指针,并处理其指向的内存对象。GO从语言层面保证同一个时间只有一个goroutine能够访问channel里面的数据,为开发者提供了一种优雅简单的工具,所以GO的做法就是使用channel来通信,通过通信来传递内存数据,使得内存数据在不同的goroutine中传递,而不是使用共享内存来通信。

加锁操作通常通过 sync 包中的 Mutex 类型来实现。Mutex(互斥锁)是一种最基本的锁机制,用于保护共享资源,确保在同一时间只有一个 goroutine 可以访问共享资源,从而避免数据竞争和并发问题。

加锁的场景,通过合理地使用锁,可以确保并发程序的正确性和稳定性,避免出现数据竞争和其他并发问题。

  1. 共享数据的读写保护: 当多个 goroutine 需要同时读写共享的数据时,为了保证数据的一致性和正确性,需要使用锁来对共享数据进行读写保护。通过加锁操作,可以确保在同一时间只有一个 goroutine 可以对共享数据进行写操作,避免出现数据竞争和并发问题。
  2. 临界区保护: 当某个代码块需要被多个 goroutine 同时访问时,为了避免多个 goroutine 同时进入临界区而导致的问题,可以使用锁来对临界区进行保护。通过在临界区的入口处加锁,在出口处解锁,可以确保在同一时间只有一个 goroutine 可以执行临界区的代码。
  3. 资源的同步访问: 在某些场景下,多个 goroutine 需要对某个资源进行同步访问,例如在并发编程中常见的信号量、互斥锁等同步机制。通过使用锁来控制资源的访问,可以保证多个 goroutine 之间的操作是有序的,避免出现数据不一致或其他并发问题。
  4. 并发数据结构的实现: 在实现并发安全的数据结构时,如并发安全的队列、栈、哈希表等,通常需要使用锁来对数据结构进行加锁保护,以确保在并发环境中的安全访问。通过使用锁来控制并发访问,可以实现高效并发的数据结构操作。
  5. 避免竞态条件: 竞态条件是指当多个 goroutine 同时访问共享资源时,由于执行顺序的不确定性而导致的程序行为不确定的情况。为了避免竞态条件,可以使用锁来对共享资源进行加锁保护,确保每次操作的原子性和一致性。

我们来认识几种加锁和不加锁的使用;

1、读写互斥锁

应用场景

适用于读多写少的场景下,才能提高程序的执行效率.

特点

  1. 读的goroutine来了获取的是读锁,后续的goroutine能读不能写
  2. 写的goroutine来了获取的是写锁,后续的goroutine不管是读还是写都要等待获取锁

使用

package mainimport ("fmt""sync""time"
)func main() {var mu sync.Mutex // 定义一个互斥锁var counter int// 启动多个 goroutine 并发地增加计数器的值for i := 0; i < 5; i++ {go func() {for j := 0; j < 1000; j++ {// 在访问共享资源之前先加锁mu.Lock()counter++// 完成对共享资源的访问后释放锁mu.Unlock()}}()}// 等待所有 goroutine 完成time.Sleep(time.Second)// 打印最终计数器的值fmt.Println("Final Counter:", counter)
}# 说明
var rwLock sync.RWMutex
rwLock.RLock() // 获取读锁
rwLock.RUnlock() // 释放读写rwLock.Lock() // 获取写锁
rwLock.Unlock() // 释放写锁

以下介绍不需要用户额外加锁的并发操作

2、等待组

应用场景

sync.Waitgroup是一种同步原语,

用来等groutine执行完再继续,是一个结构体.是值类型.给函数传参数的时候要传指针.

  • 控制程序的并发流程
  • 监控程序执行完成状态
  • 等待一组 goroutine 完成任务
  • 资源等待和释放

特点

  • WaitGroup 是线程安全的,内部使用原子操作,无需额外加锁。

使用

package mainimport ("fmt""sync""time"
)func main() {var wg sync.WaitGroupfor i := 0; i < 3; i++ {wg.Add(1) // 添加一个 goroutine 到计数器go func(id int) {defer wg.Done() // goroutine 完成任务后减少计数器fmt.Printf("Goroutine %d starting\n", id)time.Sleep(time.Second) // 模拟任务执行fmt.Printf("Goroutine %d done\n", id)}(i)}fmt.Println("Main goroutine waiting for other goroutines to finish...")wg.Wait() // 等待所有 goroutine 完成任务fmt.Println("All goroutines finished.")
}# 说明
wg.Add(1) // 起几个goroutine就加几个计数
wg.Done() // 在goroutine对应的函数中,函数要结束的时候表示goroutine完成,计数器-1
wg.Wait() // 阻塞,等待所有的goroutine都结束

2、Sync.Once

使用场景

某些函数只需要执行一次的时候,就可以使用sync.Once

比如 blog加载图片那个例子

var once sync.Onceonce.Do() // 接受一个没有参数也没有返回值的函数,如有需要可以使用闭包

特点

  • 用于执行某个函数且确保只执行一次,通常用于初始化操作

使用

package mainimport ("fmt""sync"
)func main() {var once sync.Once// 定义一个初始化函数,只会被执行一次initialize := func() {fmt.Println("Initializing...")}// 开启多个 goroutine 同时调用初始化函数for i := 0; i < 3; i++ {go func() {once.Do(initialize) // 使用 sync.Once 确保初始化函数只被执行一次}()}fmt.Println("Main goroutine waiting...")
}

3、sync.Map

使用场景

  • 缓存系统: 在缓存系统中,sync.Map 可以用来存储缓存数据,以供多个 goroutine 并发访问。它可以在不需要额外的锁机制的情况下提供并发安全的缓存存储和访问,从而提高缓存系统的性能和并发能力。
  • 全局状态管理: 在需要跨多个 goroutine 共享状态的应用程序中,sync.Map 可以用来管理全局状态。例如,一个 Web 服务器中可以使用 sync.Map 来存储用户的会话状态或其他全局状态信息。
  • 动态配置管理: 在一些需要动态加载和更新配置信息的应用程序中,sync.Map 可以用来存储配置信息,并提供并发安全的访问和更新接口。这样可以保证在配置更新的过程中不会出现数据竞争或其他并发问题。
  • 任务调度器: 在任务调度器中,sync.Map 可以用来存储任务的执行状态或其他相关信息。多个 goroutine 可以并发地读取和更新任务状态,而无需额外的锁机制,从而提高任务调度器的并发能力和性能。
  • 分布式系统中的局部缓存: 在分布式系统中,每个节点可能需要维护一个局部缓存来存储部分数据,sync.Map 可以作为局部缓存的实现。每个节点的局部缓存可以独立地进行读写操作,而无需与其他节点进行同步,从而提高系统的响应速度和吞吐量。

特点

sync.Map 是 Go 语言标准库 sync 包中提供的一种并发安全的键值对映射类型。与普通的 map 不同,sync.Map 在并发访问时不需要额外的锁机制,因此在并发场景下具有更好的性能。

使用

是一个开箱即用(不需要make初始化)的并发安全的map,

package mainimport ("fmt""sync"
)func main() {var m sync.Map// 使用 Store 方法向 sync.Map 中存储键值对m.Store("key1", "value1")m.Store("key2", "value2")m.Store("key3", "value3")// 使用 Load 方法从 sync.Map 中加载键对应的值if value, ok := m.Load("key1"); ok {fmt.Println("Value for key1:", value)} else {fmt.Println("Key1 not found")}// 使用 Range 方法遍历 sync.Map 中的所有键值对fmt.Println("All key-value pairs:")m.Range(func(key, value interface{}) bool {fmt.Println("Key:", key, "Value:", value)return true // 返回 true 继续遍历,返回 false 中止遍历})// 使用 Delete 方法从 sync.Map 中删除键值对m.Delete("key2")// 检查是否包含某个键fmt.Println("Contains key3?", m.Load("key3"))// 清空 sync.Mapm.Range(func(key, value interface{}) bool {m.Delete(key)return true})
}# 说明
// Map[key] = value // 原生map
syncMap.Store(key, value)
syncMap.Load(key)
syncMap.LoadOrStore()
syncMap.Delete()
syncMap.Range()

4、原子操作

Go语言内置了一些针对内置的基本数据类型的一些并发安全的操作;使用场景想用就用

特点

  1. 原子性: 原子操作是不可分割的,要么完全执行成功,要么完全不执行。在执行原子操作期间,不会被中断,也不会被其他 goroutine 所干扰。
  2. 并发安全: 原子操作是并发安全的,可以在多个 goroutine 并发访问时保证数据的一致性和正确性。即使多个 goroutine 同时对共享数据执行原子操作,也不会出现竞态条件(race condition)或数据竞争问题。
  3. 性能高效: 原子操作通常使用底层硬件的原子指令来实现,因此性能较高。相比于加锁机制,原子操作不需要额外的锁和同步机制,可以更快地完成操作。
  4. 适用范围广泛: 原子操作可以用于对各种类型的数据进行操作,如整型、指针等。它们可以在不同的并发场景下使用,如计数器递增、比较并交换、加载、存储等操作。
  5. 简单易用: Go 语言标准库中提供了一系列原子操作函数,使用起来非常简单直观。通过调用这些函数,开发者可以轻松地在并发程序中实现原子操作,而无需过多考虑并发安全性的问题。

使用

package mainimport ("fmt""sync""sync/atomic"
)func main() {var counter int64 // 使用 int64 类型的计数器var wg sync.WaitGroupconst numGoroutines = 10wg.Add(numGoroutines)// 多个 goroutine 并发地对计数器进行增加操作for i := 0; i < numGoroutines; i++ {go func() {for j := 0; j < 1000; j++ {atomic.AddInt64(&counter, 1) // 使用原子的增加操作}wg.Done()}()}wg.Wait() // 等待所有 goroutine 完成fmt.Println("Final Counter:", counter)
}

5、Channel

使用场景

  • goroutine 通信: Channel 是 goroutine 之间进行通信的主要方式。通过 channel,不同的 goroutine 可以安全地共享数据、进行同步操作,从而实现并发编程中的任务协作。
  • 工作池: 可以使用 channel 来实现工作池模式,将任务发送到一个任务队列中,由固定数量的 worker goroutine 来处理任务。通过 channel,可以很方便地控制 worker goroutine 的数量和任务的调度。
  • 事件通知: 可以使用 channel 来实现事件通知机制,一个 goroutine 可以向 channel 中发送事件,而其他 goroutine 可以通过监听 channel 来获取事件并进行相应的处理。
  • 计算结果收集: 在并发计算中,可以使用 channel 来收集各个 goroutine 计算得到的结果,并在所有结果都就绪后进行汇总或其他操作。
  • 超时控制: 可以使用 channel 来实现超时控制机制,例如通过 time.After 函数返回的 channel 来实现某个操作的超时判断。

特点

  • 安全性: Channel 是并发安全的,多个 goroutine 可以同时对一个 channel 进行读写操作,而不会发生数据竞争或其他并发问题。这是因为 channel 内部实现了同步机制,能够确保数据传递的安全性。
  • 阻塞操作: 当向一个已满的 channel 发送数据时,发送操作会阻塞直到有其他 goroutine 从该 channel 中接收数据;当从一个空的 channel 接收数据时,接收操作会阻塞直到有其他 goroutine 向该 channel 发送数据。这种阻塞操作使得 goroutine 之间的通信更加简洁和可靠。
  • 单向传输: Channel 支持单向传输,即可以指定 channel 只能用于发送数据或只能用于接收数据。这样可以在一定程度上增强代码的可读性和安全性。
  • 关闭通知: 可以通过关闭 channel 来向接收方通知数据流的结束。接收方可以通过检查 channel 的关闭状态来判断是否还有数据需要处理。
  • 引用类型: Channel 是引用类型,可以像其他引用类型一样进行传递、赋值和比较。这使得在函数间传递 channel 变得非常方便。

使用

package mainimport ("fmt""time"
)func sender(ch chan<- int) {for i := 0; i < 5; i++ {ch <- i // 向通道发送数据time.Sleep(time.Second)}close(ch) // 关闭通道
}func receiver(ch <-chan int) {for num := range ch { // 从通道接收数据,直到通道关闭fmt.Println("Received:", num)}
}func main() {ch := make(chan int) // 创建一个整型通道go sender(ch)   // 启动发送数据的 goroutinego receiver(ch) // 启动接收数据的 goroutinetime.Sleep(6 * time.Second) // 等待一段时间,确保 goroutine 有足够的时间执行
}

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

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

相关文章

Stm32CubeMX 为 stm32mp135d 添加 spi

Stm32CubeMX 为 stm32mp135d 添加 spi 一、启用设备1. spi 设备添加2. spi 引脚配置2. spi 时钟配置 二、 生成代码1. optee 配置 spi 时钟和安全验证2. linux spi 设备 dts 配置 bringup 可参考&#xff1a;Stm32CubeMX 生成设备树 一、启用设备 1. spi 设备添加 选中spi设…

使用UmcFramework和unimrcpclient.xml连接多个SIP设置的配置指南及C代码示例

使用UmcFramework和unimrcpclient.xml连接多个SIP设置的配置指南及C代码示例 引言1. UniMRCP和UmcFramework简介2. 准备工作3. unimrcpclient.xml配置文件3.1 定义SIP设置3.2 定义MRCP会话配置文件 4. C代码示例5. 测试和验证6. 故障排查7. 结论8. 参考文献 引言 在多媒体通信…

js语法---map,set结构

map和set是像对象和数组一样的数据结构集&#xff0c;内部可以按照一定的关系存放一串数据 map map是一个带键的数据项的集合&#xff0c;就像一个 Object 一样。 但是它们最大的差别是 Map 允许任何类型的键&#xff08;key&#xff09;。 它的方法和属性如下&#xff1a; n…

后端方案设计文档结构模板可参考

文章目录 1 方案设计文档整体结构2 方案详细设计2.1 概要设计2.2 详细设计方案2.2.1 需求分析2.2.2 业务流程设计2.2.3 抽象类&#xff1a;实体对象建模2.2.4 接口设计2.2.5 存储设计 1 方案设计文档整体结构 一&#xff0c;现状&#xff1a;把项目的基本情况和背景都说清楚&a…

SpringCloud学习笔记(二)Ribbon负载均衡、Nacos注册中心、Nacos与Eureka的区别

文章目录 4 Ribbon负载均衡4.1 负载均衡原理4.2 源码解读4.3 负载均衡策略4.3.1 内置的负载均衡策略4.3.2 自定义负载均衡策略4.3.2.1 方式一&#xff1a;定义IRule4.3.2.2 方式二&#xff1a;配置文件 4.4 饥饿加载 5 Nacos注册中心5.1 认识和安装Nacos5.2 服务注册到Nacos5.3…

【linux-1-Ubuntu常用命令-vim编辑器-Vscode链接ubuntu远程开发】

目录 1. 安装虚拟机Vmare和在虚拟机上安装Ubuntu系统&#xff1a;2. 常用的Ubuntu常识和常用命令2.1 文件系统结构2.2 常用命令2.3 vim编辑器 3. Ubuntu能联网但是ping不通电脑&#xff1a;4. Windows上安装VScode链接ubuntu系统&#xff0c;进行远程开发&#xff1a; 1. 安装虚…

qt5-入门-2D绘图-Graphics View 架构

参考&#xff1a; Qt Graphics View Framework_w3cschool https://www.w3cschool.cn/learnroadqt/4mvj1j53.html C GUI Programming with Qt 4, Second Edition 本地环境&#xff1a; win10专业版&#xff0c;64位&#xff0c;Qt 5.12 基础知识 QPainter比较适合少量绘图的情…

【webrtc】MessageHandler 2: 基于线程的消息处理:以PeerConnectionClient为例

PeerConnectionClient 前一篇 nullaudiopoller 并么有场景线程,而是就是在当前线程直接执行的, PeerConnectionClient 作为一个独立的客户端,默认的是主线程。 PeerConnectionClient 同时维护客户端的信令状态,并且通过OnMessage实现MessageHandler 消息处理。 目前只处理一…

【分享】如何将word格式文档转化为PDF格式

在日常的办公和学习中&#xff0c;我们经常需要将Word文档转换为PDF格式。PDF作为一种通用的文件格式&#xff0c;具有跨平台、易读性高等优点&#xff0c;因此在许多场合下都更为适用。那么&#xff0c;如何实现Word转PDF呢&#xff1f;本文将介绍几种常用的方法&#xff0c;帮…

Aker(安碁科技)晶振产品应用和选型

一、石英晶体振荡器简介 在电子电路系统中&#xff0c;特定的动作需要严格按照一定的顺序进行&#xff0c;以确保数据被正确处理和操作&#xff0c;时钟信号就成了系统工作的重要引导者。而且在多模块复杂电路系统中&#xff0c;为了确保不同功能模块能协调一致地工作&#xf…

怎么用微信小程序实现远程控制台球室

怎么用微信小程序实现远程控制台球室呢&#xff1f; 本文描述了使用微信小程序调用HTTP接口&#xff0c;实现控制台球室&#xff0c;控制球台上方的照明灯&#xff0c;单台设备可控制多张球台的照明灯。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 …

Matlab生成txt文件导入到Vivado仿真

Matlab处理数据并将其写入txt文件 %% Txt Generate pre_RS_datadec2bin(simDataIn,8); %将数据转化为8bit的二进制 fidfopen("F:\FPGA\Xilinx_vivado\project\dvbstestbench\dbvs\matlab\pre_RS_data.txt","wt"); for i1:n*nMessages %数据…