Go中为什么不建议用锁?

news2024/11/19 7:43:24

在这里插入图片描述

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 main

import (
	"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 main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup

	for 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.Once

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

特点

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

使用

package main

import (
	"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 main

import (
	"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.Map
	m.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 main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var counter int64 // 使用 int64 类型的计数器

	var wg sync.WaitGroup
	const numGoroutines = 10
	wg.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 main

import (
	"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)   // 启动发送数据的 goroutine
	go receiver(ch) // 启动接收数据的 goroutine

	time.Sleep(6 * time.Second) // 等待一段时间,确保 goroutine 有足够的时间执行
}

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

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

相关文章

Java项目:88 springboot104学生网上请假系统设计与实现

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本学生网上请假系统管理员&#xff0c;教师&#xff0c;学生。 管理员功能有个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;…

22 重构系统升级-实现不停服的数据迁移和用户切量

专栏的前 21 讲&#xff0c;从读、写以及扣减的角度介绍了三种特点各异的微服务的构建技巧&#xff0c;最后从微服务的共性问题出发&#xff0c;介绍了这些共性问题的应对技巧。 在实际工作中&#xff0c;你就可以参考本专栏介绍的技巧构建新的微服务&#xff0c;架构一个具备…

vue3 安装-使用之第一篇

首先需要node版本高于V16.14.1 安装 执行 npm create vitelatest 具体选择按照自己实际需要的来 Project name:项目名称 Select a framework:选择用哪种框架 &#xff08;我选择vue&#xff09; Select a variant: 选择用JS还是TS&#xff08;我选择JS&#xff09;找到项目&…

STM32 HAL库F103系列之IIC实验

IIC总线协议 IIC总线协议介绍 IIC&#xff1a;Inter Integrated Circuit&#xff0c;集成电路总线&#xff0c;是一种同步 串行 半双工通信总线。 总线就是传输数据通道 协议就是传输数据的规则 IIC总线结构图 ① 由时钟线SCL和数据线SDA组成&#xff0c;并且都接上拉电阻…

(7)快速调优

文章目录 前言 1 安装脚本 2 运行 QuikTune 3 高级配置 前言 VTOL QuikTune Lua 脚本简化了为多旋翼飞行器的姿态控制参数寻找最佳调整的过程。 脚本会缓慢增加相关增益&#xff0c;直到检测到振荡。然后&#xff0c;它将增益降低 60%&#xff0c;并进入下一个增益。所有增…

微服务保护和分布式事务(Sentinel、Seata)笔记

一、雪崩问题的解决的服务保护技术了解 二、Sentinel 2.1Sentinel入门 1.Sentinel的安装 &#xff08;1&#xff09;下载Sentinel的tar安装包先 &#xff08;2&#xff09;将jar包放在任意非中文、不包含特殊字符的目录下&#xff0c;重命名为 sentinel-dashboard.jar &…

Spark Structured Streaming 分流或双写多表 / 多数据源(Multi Sinks / Writes)

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

数据库管理-第179期 分库分表vs分布式(20240430

数据库管理179期 2024-04-30 数据库管理-第179期 分库分表vs分布式&#xff08;20240430&#xff09;1 分库分表1.1 分库1.2 分表1.3 组合1.4 问题 2 分布式3 常见分布式数据库4 期望总结 数据库管理-第179期 分库分表vs分布式&#xff08;20240430&#xff09; 作者&#xff1…

git 第一次安装设置用户名密码

git config --global user.name ljq git config --global user.email 15137659164qq.com创建公钥命令 输入后一直回车 ssh-keygen -t rsa下面这样代表成功 这里是公钥的 信息输入gitee 中 输入下面命令看是否和本机绑定成功 ssh -T gitgitee.com如何是这样&#xff0c;恭喜…

spring的高阶使用技巧1——ApplicationListener注册监听器的使用

Spring中的监听器&#xff0c;高阶开发工作者应该都耳熟能详。在 Spring 框架中&#xff0c;这个接口允许开发者注册监听器来监听应用程序中发布的事件。Spring的事件处理机制提供了一种观察者模式的实现&#xff0c;允许应用程序组件之间进行松耦合的通信。 更详细的介绍和使…

Flask简介

Flask简介 安装概述使用PyCharm创建一个Flask程序 Flask程序的基本结构初始化路由和视图函数启动服务器请求-响应循环 安装 概述 Flask算是小型框架&#xff0c;小到可以称为“微框架”。Flask 非常小&#xff0c;因此你一旦能够熟练使用它&#xff0c;很可能就能读懂它所有的…

element的el-table 解决表格多页选择数据时,数据被清空

问题&#xff1a;切换页码时&#xff0c;勾选的数据会被清空 重点看我圈出来的&#xff0c;直接复制&#xff0c;注意&#xff0c;我这里 return row.productId;一般大家的是 return row.id,根据接口定的唯一变量 :row-key"getRowKeys"​​​​​​​:reserve-sele…

【八大排序(三)】快速排序

❣博主主页: 33的博客❣ ▶️文章专栏分类:八大排序◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多排序知识 目录 1.前言2.快速排序2.1概念2.2画图理解2.3递归代码实现2.3.1Hoare法2.3.2挖坑法2.3.3前…

vue3 引用虚拟键盘simple-keyboard

simple-keyboard官网地址&#xff1a;https://virtual-keyboard.js.org 目前实现效果图是&#xff08;实现数字、大小写字母键盘&#xff09;&#xff1a; 1.需要先安装simple-keyboard npm install simple-keyboard --save2.封装sinpleKeyboard 组件 <!-- keyboard-bo…

24深圳杯数学建模挑战赛A题6页初步思路+参考论文+保姆级答疑!!!

问题1:单个残骸的精确位置定位 建立数学模型&#xff0c;分析如果要精准确定空中单个残骸发生音爆时的位置坐标&#xff08;经度、纬度、高程&#xff09;和时间&#xff0c;至少需要布置几台监测设备&#xff1f;假设某火箭一级残骸分离后&#xff0c;在落点附近布置了7台监测…

聊聊 ASP.NET Core 中间件(一):一个简单的中间件例子

前言&#xff1a;什么是中间件 服务器在收到 HTTP 请求后会对用户的请求进行一系列的处理&#xff0c;比如检查请求的身份验证信息、处理请求报文头、检查是否存在对应的服务器端响应缓存、找到和请求对应的控制器类中的操作方法等&#xff0c;当控制器类中的操作方法执行完成…

02 spring-boot+mybatis+elementui 的登录,文件上传,增删改查的入门级项目

前言 主要是来自于 朋友的需求 项目概况 就是一个 学生信息的增删改查 然后 具体到业务这边 使用 mybatis xml 来配置的增删改查 后端这边 springboot mybatis mysql fastjson hutool 的一个基础的增删改查的学习项目, 简单容易上手 前端这边 node14 vue element…

【C++】初识string类

一、熟悉string类 1.1 string类的由来&#xff1a; C语音中的字符串需要我们自己管理底层空间&#xff0c;容易内存泄露。而C是面向对象语音&#xff0c;所以它把字符串封装成一个string类。 C中对于string的定义为&#xff1a;typedef basic_string string; 也就是说C中的str…

Microsoft Universal Print 与 SAP 集成教程

引言 从 SAP 环境打印是许多客户的要求。例如数据列表打印、批量打印或标签打印。此类生产和批量打印方案通常使用专用硬件、驱动程序和打印解决方案来解决。 Microsoft Universal Print 是一种基于云的打印解决方案&#xff0c;它允许组织以集中化的方式管理打印机和打印机驱…

【网站项目】个性化商铺系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…