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 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 有足够的时间执行
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

亚马逊关键字搜索商品列表API接口:探索海量商品的利器

亚马逊关键字搜索商品列表API接口允许开发者通过输入关键字或特定参数&#xff0c;在亚马逊平台上进行商品搜索&#xff0c;并返回符合搜索条件的商品列表信息。这些信息包括商品的标题、图片、价格、评价等&#xff0c;为商家、开发者以及市场分析师提供了丰富的商品数据支持。…

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

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

C#调用skiasharp操作并绘制图片

之前学习ViewFaceCore时采用Panel控件和GDI将图片及识别出的人脸方框和关键点绘制出来&#xff0c;本文将其修改为基于SKControl和SKCanvas实现相同的显示效果并支持保存为本地图片。   新建Winform项目&#xff0c;在Nuget包管理器中搜索并安装一下SkiaSharp和ViewFaceCore…

三维SDMTSP:GWO灰狼优化算法求解三维单仓库多旅行商问题,可以更改数据集和起点(MATLAB代码)

一、单仓库多旅行商问题 多旅行商问题&#xff08;Multiple Traveling Salesman Problem, MTSP&#xff09;是著名的旅行商问题&#xff08;Traveling Salesman Problem, TSP&#xff09;的延伸&#xff0c;多旅行商问题定义为&#xff1a;给定一个&#x1d45b;座城市的城市集…

springboot 集成 flowable

随着企业对于业务流程管理需求的增加&#xff0c;流程引擎在企业信息化建设中的作用越来越重要。Flowable是一个开源的轻量级业务流程管理&#xff08;BPM&#xff09;和工作流引擎&#xff0c;它支持BPMN 2.0标准。 Flowable的一些特点&#xff1a; 安装集成&#xff1a;Flow…

hdfs安全模式

hdfs安全模式 1.安全模式 查看hdfs是否在安全模式&#xff1a;不能上传数据 删除 修改 但是能查看 ------------------------ $>hdfs dfsadmin -safemode enter //进入 $>hdfs dfsadmin -safemode get //查看 $>hdfs dfsadmin -saf…

巧用 TiCDC Syncpiont 构建银行实时交易和准实时计算一体化架构

本文阐述了某商业银行如何利用 TiCDC Syncpoint 功能&#xff0c;在 TiDB 平台上构建一个既能处理实时交易又能进行准实时计算的一体化架构&#xff0c;用以优化其零售资格业务系统的实践。通过迁移到 TiDB 并巧妙应用 Syncpoint&#xff0c;该银行成功解决了原有多个 MySQL 集…

解决TIVA飞控玄学类问题的通解,用魔法打败魔法

问题&#xff1a;我遭遇了玄学问题&#xff0c;出现飞机在起降过程中&#xff0c;位置晃动&#xff0c;突然出现的&#xff0c;昨天还好好的&#xff0c;位置地点都没换&#xff0c;今天中午测试了5、6次每次都这样&#xff0c;现在茫然无措&#xff0c;小哥救我&#xff1f; 这…

数据库管理-第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…

vue路由(路由基本使用,传参,多级路由)

目录 vue-router简介路由配置和使用嵌套&#xff08;多级&#xff09;路由路由传参方式1&#xff1a;路由的query参数方式2&#xff1a;路由的params参数props配置 命名路由取消路由组件在前进后退 vue-router简介 vue的一个插件库&#xff0c;专门用来实现SPA应用 路由配置…

k8s环境prometheus operator监控集群外资源

文章目录 k8s环境添加其他节点基于prometheus operator k8s环境prometheus operator添加node-exporter方式一&#xff1a;通过 ServiceMonitor 方式可以写多个监控node节点运行 external-node.yaml查看资源有没有被创建热更新 外部需要被监控服务器安装 node-exporterdocker 方…

git如何将多个commit合并成一个?

我们使用git进行版本控制&#xff0c;在本地开发完某个功能时&#xff0c;需要提交commit&#xff0c;然后push至开发分支。简单的功能还好&#xff0c;几个commit可能就好了。但是如果功能比较复杂&#xff0c;commit多达十几甚至几十个时&#xff0c;commit管理就会很冗长。比…

使用Pandas和Matplotlib实现数据探索性可视化

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用 Pandas 和 Matplotlib 实现数据探索性可视化 在数据分析和机器学习领域&#xff0c;数…

Apache POI 在java中处理excel

介绍: Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。 一般情况下&#xff0c;POI 都是用于操作 Excel 文件。 如何使用: 1.maven坐标引入 <depend…

实验八智能手机互联网程序设计(微信程序方向)实验报告

请在上一次实验的基础之上完成“手机快速注册”页面、“企业用户注册”页面&#xff0c;并实现点击手机快速注册和企业用户注册后转跳至该页面在“手机快速注册”页面&#xff0c;输入框内输入内容并失去焦点后&#xff0c;下方的按钮会变化 在企业用户注册页面&#xff0c;用户…

思考!思考!jmeter线程数≠用户并发数

最近又在搞性能测试了&#xff0c;相较于之前的写脚本出数据就完事&#xff0c;这次深入的思考了一下测试出来的指标&#xff0c;到底有什么意义&#xff1f;&#xff1f;&#xff1f; 绞尽脑汁思考了好几天&#xff0c;终于有了点思路&#xff0c;写出来与大家分享&#xff0…

掌握JavaScript面向对象编程核心密码:深入解析JavaScript面向对象机制对象基础、原型模式与继承策略全面指南,高效创建高质量、可维护代码

ECMAScript&#xff08;简称ES&#xff0c;是JavaScript的标准规范&#xff09;支持面向对象编程&#xff0c;通过构造函数模拟类&#xff0c;原型链实现继承&#xff0c;以及ES6引入的class语法糖简化面向对象开发。对象可通过构造函数创建&#xff0c;使用原型链共享方法和属…

基于RK1126的小型化低功耗AI相机,支持人体特征识别、人脸特征识别、案例帽识别等

提供可定制的⼀套 AI相机软硬件开发平台&#xff0c; 硬件采⽤ RockchipRV1126处理器解决 ⽅案&#xff0c;可选择搭配 SonyIMX系列传感器&#xff0c;POE供电与数据传输&#xff0c;采⽤ 38板标准结构设计&#xff0c;快速按需定制外壳&#xff0c;⽀撑从开发到验证到批量⽣产…

基于Matlab使用深度学习的多曝光图像融合

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 在图像处理领域&#xff0c;多曝光图像融合技术是一种重要的技术&#xff0c;它可以将不同曝光条件下…

Ubuntu安装Neo4j

Ubuntu&#xff08;在线版&#xff09; 更新软件源 sudo apt-get update 添加Neo4j官方存储库 wget -O - https://debian.neo4j.com/neotechnology.gpg.key | sudo apt-key add - 将地址添加到系统的软件包源列表中 echo deb https://debian.neo4j.com stable latest | su…