GO学习之 同步操作sync包

2023-11-05 05:28
文章标签 go 学习 sync 同步操作

本文主要是介绍GO学习之 同步操作sync包,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)
9、GO学习之 接口(Interface)
10、GO学习之 网络通信(Net/Http)
11、GO学习之 微框架(Gin)
12、GO学习之 数据库(mysql)
13、GO学习之 数据库(Redis)
14、GO学习之 搜索引擎(ElasticSearch)
15、GO学习之 消息队列(Kafka)
16、GO学习之 远程过程调用(RPC)
17、GO学习之 goroutine的调度原理
18、GO学习之 通道(nil Channel妙用)
19、GO学习之 同步操作sync包

文章目录

  • GO系列
  • 前言
  • 一、channel 和 sync包
    • 1.1 channel 和 sync包性能测试
    • 1.2 channel 和 sync包如何选择
  • 二、sync包使用注意事项
  • 三、sync.Mutex 原理
  • 四、小结

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
同步操作在并发中是必不可少的的,若对一些公用的资源进行操作,为了保证操作的原子和一致性,就需要使用到锁来进行控制。
Go 语言在提供CSP(通信顺序进程)并发模型原语的的同时,还提供了标准库 sync包 针对传统基于共享内存并发模型的同步原语,包括 互斥锁(sync.Mutex)、读写锁(sync.RWMutex)、条件锁(sync.Cond)等。

一、channel 和 sync包

  • Go 语言提倡不要通过共享内存来通信,而是要通过通信来共享内存,一般的场景中,先使用 CSP 并发模型实现,就是 goroutine + channel 编程。但也有一些特殊的场景,需要 sync包 提供的低级同步原语。
  • 在 Go 中,channel 属于高级同步原语,其实现是建立在低级同步原语之上的,所以 channel 自身的性能与低级同步原语相比要稍微逊色一点了。因此,在需要需要高性能的情况下,sync包 提供的低同步原语更为重要。

1.1 channel 和 sync包性能测试

我们来对 channel 和 sync包 的性能做一个对比,示例代码如下(channel_sync_test.go ):
注意!!!

  1. 基准测试代码文件必须是_test.go结尾,和单元测试一样;
  2. 基准测试的函数以Benchmark开头;
  3. 参数为 *testing.B;
  4. 基准测试函数不能有返回值;
  5. b.ResetTimer是重置计时器,这样可以避免for循环之前的初始化代码的干扰;
  6. b.N是基准测试框架提供的,是循环次数,无需关心;
  7. go test -bench . .\channel_sync_test.go 运行;
package mainimport ("sync""testing"
)var data = 0// 声明互斥锁
var mu sync.Mutex// 声明一个通道
var ch = make(chan struct{}, 1)func syncByMutex() {mu.Lock()data++mu.Unlock()
}func syncByChannel() {ch <- struct{}{}data++<-ch
}// 基准测试函数以 Benchmark 开头
func BenchmarkSectionByMutex(b *testing.B) {for i := 0; i < b.N; i++ {syncByMutex()}
}// 基准测试函数以 Benchmark 开头
func BenchmarkSectionByChannel(b *testing.B) {for i := 0; i < b.N; i++ {syncByChannel()}
}

运行结果:

PS D:\workspaceGo\src\sync> go test -bench . .\channel_sync_test.go
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkSectionByMutex-8       79662760                13.15 ns/op
BenchmarkSectionByChannel-8     27814993                40.38 ns/op
PASS
ok      command-line-arguments  2.523s

从运行结果中看,BenchmarkSectionByChannel 测试函数是 40.38 ns/opBenchmarkSectionByMutex13.15 ns/op,很明显 sync包 的的性能更佳。

1.2 channel 和 sync包如何选择

  • 同步访问 channel 的并发设计的特点是,在 goroutine 间通过 channel 转移数据对象的所有权。只有获得了数据对象的所有权(就是从 channel 中获得该对象)才可以对数据对象进行状态变更。
  • 如果设计中没有转移结构体对象所有权,但又要保证结构体内部状态能在多个 goroutine 之间同步,那么你可以使用 sync包 提供的低级同步原语来实现,比如 sync.Mutex(互斥锁)。

二、sync包使用注意事项

在 sync 包中,有这么些注释:

  • Values containing the types defined in this package should not be copied. (不应该包含那些包含了此包中类型的值)
  • A Mutex must not be copied after first use. (禁止复制首次使用后的 Mutex)

还有其他 sync包 中也有诸如此类注释,那是为什么呢?我们来进行一个小 demo:

package mainimport ("log""sync""time"
)// 声明一个结构体 data
type data struct {n intsync.Mutex
}func main() {// 声明一个结构体对象 dd := data{n: 100}// 启动一个线程进行加锁操作go func(d data) {for {log.Println("go 2 try to lock...")d.Lock()log.Println("go 2 locked ok...")time.Sleep(3 * time.Second)d.Unlock()log.Println("go 2 unlock ok...")}}(d)d.Lock()log.Println("go main lock ok...")// 在 Mutex 首次使用后复制值go func(d data) {log.Println("go 1 try lock...")d.Lock()log.Println("go 1 locked ok...")time.Sleep(3 * time.Second)d.Unlock()log.Println("go 1 unlock ok...")}(d)time.Sleep(1000 * time.Second)d.Unlock()log.Println("go main unlock ok...")
}

运行结果:

PS D:\workspaceGo\src\sync> go run .\sync.go
2023/11/04 16:57:24 go main lock ok...
2023/11/04 16:57:24 go 2 try to lock...
2023/11/04 16:57:24 go 2 locked ok...
2023/11/04 16:57:24 go 1 try lock...
2023/11/04 16:57:27 go 2 unlock ok...
2023/11/04 16:57:27 go 2 try to lock...
2023/11/04 16:57:27 go 2 locked ok...
2023/11/04 16:57:30 go 2 unlock ok...
2023/11/04 16:57:30 go 2 try to lock...
2023/11/04 16:57:30 go 2 locked ok...
...

在示例中创建了两个 goroutine :go 1 和 go 2,从运行结果中看到 go 1 阻塞在了加锁操作上了,则 go 2 则是按照预期正常运行。go 1 和 go 2 的区别就在于 go 2 是在互斥锁首次使用之前创建的,而 go 1 则是在互斥锁加锁操作并且在锁定状态之后创建的,并且程序在创建 go 1的时候复制了 data 的实例并且使用了这个副本。

三、sync.Mutex 原理

我们可以在 $GOROOT/src/sync/mutex.go 源码中看到如下声明语句:

type Mutex struct {state int32sema  uint32
}

其实 sync.Mutex 的实现很简单,定义了两个字段 state 和 sema。

  • state:表示当前互斥锁的状态
  • sema:用户控制状态的信号量

对Mutex实例的复制即是对两个整型字段的复制。在初始状态下,Mutex 示例处于 Unlocked 状态(state:0,sema:0),上述案例中,go 2 在复制了初始状态的 Mutex 实例,副本的 state 和 sema 均为 0,则与 go 2新定义的 Mutex 无异,则go 2可以继续正常运行。

后续主程序调用了 Lock 方法,Mutex 实例变为 Locked 状态,而此后 go 1 创建是恰恰复制了处于 Locked 状态的实例,那副本实例也是 Locked 状态的,所以 go 1 进去了阻塞状态(也是死锁状态,因为没有任何机会调用 Unlock 了)。

四、小结

通过本文案例可以直观的看到,sync包中的实例在首次实例化后被复制的副本一旦被使用了将导致不可预期的结果。所以在使用 sync包 的时候,推荐通过 闭包 或者 传递类型实例 的地址或指针的方式进行,这是使用 sync包 需要注意的地方。


现阶段还是对 Go 语言的学习阶段,想必有一些地方考虑的不全面,本文示例全部是亲自手敲代码并且执行通过。
如有问题,还请指教。
评论去告诉我哦!!!一起学习一起进步!!!

这篇关于GO学习之 同步操作sync包的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/347706

相关文章

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

Go语言中最便捷的http请求包resty的使用详解

《Go语言中最便捷的http请求包resty的使用详解》go语言虽然自身就有net/http包,但是说实话用起来没那么好用,resty包是go语言中一个非常受欢迎的http请求处理包,下面我们一起来学... 目录安装一、一个简单的get二、带查询参数三、设置请求头、body四、设置表单数据五、处理响应六、超

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

Golang基于内存的键值存储缓存库go-cache

《Golang基于内存的键值存储缓存库go-cache》go-cache是一个内存中的key:valuestore/cache库,适用于单机应用程序,本文主要介绍了Golang基于内存的键值存储缓存库... 目录文档安装方法示例1示例2使用注意点优点缺点go-cache 和 Redis 缓存对比1)功能特性

Go 1.23中Timer无buffer的实现方式详解

《Go1.23中Timer无buffer的实现方式详解》在Go1.23中,Timer的实现通常是通过time包提供的time.Timer类型来实现的,本文主要介绍了Go1.23中Timer无buff... 目录Timer 的基本实现无缓冲区的实现自定义无缓冲 Timer 实现更复杂的 Timer 实现总结在

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

Go路由注册方法详解

《Go路由注册方法详解》Go语言中,http.NewServeMux()和http.HandleFunc()是两种不同的路由注册方式,前者创建独立的ServeMux实例,适合模块化和分层路由,灵活性高... 目录Go路由注册方法1. 路由注册的方式2. 路由器的独立性3. 灵活性4. 启动服务器的方式5.

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要