一文搞懂Go语言互斥锁、读写锁【线程安全】

简介: 一文搞懂Go语言互斥锁、读写锁【线程安全】

文章目录



前言


单个线程时数据操作的只有一个线程,数据的修改也只有一个线程参与,数据相对来说是安全的,多线程时对数据操作的不止一个线程,所以同时对数据进行修改的时候难免紊乱


一、互斥锁是什么?


1.概念


互斥锁是为了并发的安全,在多个goroutine共同工作的时候,对于共享的数据十分不安全

写入时容易因为竞争造成数据不必要的丢失。

互斥锁一般加在共享数据修改的地方。


2.未加锁


  线程不安全,操作的全局变量会计算异常
package main
import (
  "fmt"
  "sync"
)
var x int = 0
var wg sync.WaitGroup
func add() {
  defer wg.Done()
  for i := 0; i < 5000; i++ {
    x++
  }
}
func main() {
  wg.Add(2)
  go add()
  go add()
  wg.Wait()
  fmt.Println(x)
}
/*
打印结果:(每次打印不一样,正常的结果应该是10000)
  6051
  5059
  5748
  10000
*/


3.加锁之后


线程安全,全局变量计算无异常


package main
import (
  "fmt"
  "sync"
)
var x int = 0
var wg sync.WaitGroup
// 创建一个锁对象
var lock sync.Mutex
func add() {
  defer wg.Done()
  for i := 0; i < 5000; i++ {
    //加锁
    lock.Lock()
    x++
    //解锁
    lock.Unlock()
  }
}
func main() {
  wg.Add(2)
  //开启两个线程
  go add()
  go add()
  wg.Wait()
  fmt.Println(x)
}
/*
打印结果:
  全为10000
*/


二、读写锁【效率革命】


1.为什么读写锁效率高


  使用锁的时候,安全与效率往往需要互相转换
  在对数据进行操作的时候,只会进行数据的读与写
  而读与读之间可以同时进行,读与写之间需要保证写的时候不去读
  此时为了提高效率就发明读写锁,在读写锁机制下,安全没有丝毫降低,但效率进行了成倍的提升
  提升的效率在读与写操作次数差异越大时越明显


2.使用方法


代码如下(示例):


package main
import (
  "fmt"
  "sync"
  "time"
)
var (
  x      = 0
  rwlock sync.RWMutex
  wg     sync.WaitGroup
)
func write() {
  defer wg.Done()
  rwlock.Lock()
  x++
  rwlock.Unlock()
}
func read() {
  wg.Done()
  //开启读锁
  rwlock.RLock()
  fmt.Println(x)
  //释放读锁
  rwlock.RUnlock()
}
func main() {
  start := time.Now()
  for i := 0; i < 100; i++ {
    wg.Add(1)
    go write()
  }
  // time.Sleep(time.Second)
  for i := 0; i < 10000; i++ {
    wg.Add(1)
    go read()
  }
  wg.Wait()
  fmt.Println(time.Now().Sub(start))
}


三、sync.once


1.sync.once产生背景:


  在多个goroutine中往往会由于线程不同步造成数据读写的冲突,特别是在进行文件打开对象创建的时候
  可能会造成向关闭的文件写内容,使用未初始化的对象,或者对一个对象进行多次初始化。


2.sync.once机制概述:


  sync.once保证函数内的代码只执行一次
  实现的机制是在once内部有一个标志位,在执行代码的时候执行一次之后标志位将置为1
  后续判断标志位,如果标志位被改为1则无法再进行操纵


3.sync.once注意点:


  sync.Once.Do()传进去的函数参数无参无返
  一个once对象只能执行一次Do方法,向Do方法内传多个不同的函数时只能执行第一个传进去的
  传进去Do方法的函数无参无返,可以用函数闭包把需要的变量传进去


4.使用方法


  一般结合并发使用,旨在对通道或文件只进行一次关闭


func f2(a <-chan int, b chan<- int) {
  for {
    x, ok := <-a
    if !ok {
      break
    }
    fmt.Println(x)
    b <- x * 10
  }
  // 确保b通道只关闭一次
  once.Do(func() {
    close(b)
  })
}


四、atomic原子包操作


  原子包将指定的数据进行安全的加减交换操作
  网上还有一大堆关于原子包的api感兴趣的小伙伴可以自行百度,这里就不细细阐述了


package main
import (
  "fmt"
  "sync"
  "sync/atomic"
)
var x int64 = 0
var wg sync.WaitGroup
/*
  原子操作是将数据进行打包枷锁,直接通过指定的函数进行相应的操作
  可以使用load读取、store写入、add修改、swap交换。
  // 类似于读取一个变量、对一个变量进行赋值
*/
func addone() {
  // 没有加锁进行并发的话,会产生数据丢失的情况
  defer wg.Done()
  // x++
  // 不用加锁也可以使用的行云流水
  // 第一个参数是进行操作的数据,第二个是增加的步长
  atomic.AddInt64(&x, 1)
}
func csf() {
  // 进行比较相等则将新值替换旧值
  ok := atomic.CompareAndSwapInt64(&x, 100, 200)
  fmt.Println(ok, x)
}
func main() {
  for i := 0; i < 50000; i++ {
    wg.Add(1)
    go addone()
  }
  wg.Wait()
  fmt.Println(x)
  x = 100
  csf()
  fmt.Println(123)
}


总结


读写锁区分读者和写者,而互斥锁不区分 互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个写者, 但是允许多个读者同时读对象。 联系:读写锁在获取写锁的时候机制类似于互斥锁。

目录
相关文章
|
2天前
|
程序员 Go PHP
为什么大部分的 PHP 程序员转不了 Go 语言?
【9月更文挑战第8天】大部分 PHP 程序员难以转向 Go 语言,主要因为:一、编程习惯与思维方式差异,如语法风格和编程范式;二、学习成本高,需掌握新知识体系且面临项目压力;三、职业发展考量,现有技能价值及市场需求不确定性。学习新语言虽有挑战,但对拓宽职业道路至关重要。
24 10
|
2天前
|
算法 程序员 Go
PHP 程序员学会了 Go 语言就能唬住面试官吗?
【9月更文挑战第8天】学会Go语言可提升PHP程序员的面试印象,但不足以 solely “唬住” 面试官。学习新语言能展现学习能力、拓宽技术视野,并增加就业机会。然而,实际项目经验、深入理解语言特性和综合能力更为关键。全面展示这些方面才能真正提升面试成功率。
21 10
|
2天前
|
编译器 Go
go语言学习记录(关于一些奇怪的疑问)有别于其他编程语言
本文探讨了Go语言中的常量概念,特别是特殊常量iota的使用方法及其自动递增特性。同时,文中还提到了在声明常量时,后续常量可沿用前一个值的特点,以及在遍历map时可能遇到的非顺序打印问题。
|
6天前
|
安全 大数据 Go
深入探索Go语言并发编程:Goroutines与Channels的实战应用
在当今高性能、高并发的应用需求下,Go语言以其独特的并发模型——Goroutines和Channels,成为了众多开发者眼中的璀璨明星。本文不仅阐述了Goroutines作为轻量级线程的优势,还深入剖析了Channels作为Goroutines间通信的桥梁,如何优雅地解决并发编程中的复杂问题。通过实战案例,我们将展示如何利用这些特性构建高效、可扩展的并发系统,同时探讨并发编程中常见的陷阱与最佳实践,为读者打开Go语言并发编程的广阔视野。
|
4天前
|
存储 Shell Go
Go语言结构体和元组全面解析
Go语言结构体和元组全面解析
|
9天前
|
Go
golang语言之go常用命令
这篇文章列出了常用的Go语言命令,如`go run`、`go install`、`go build`、`go help`、`go get`、`go mod`、`go test`、`go tool`、`go vet`、`go fmt`、`go doc`、`go version`和`go env`,以及它们的基本用法和功能。
20 6
|
9天前
|
存储 Go
Golang语言基于go module方式管理包(package)
这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。
16 3
|
11天前
|
缓存 安全 Java
如何利用Go语言提升微服务架构的性能
在当今的软件开发中,微服务架构逐渐成为主流选择,它通过将应用程序拆分为多个小服务来提升灵活性和可维护性。然而,如何确保这些微服务高效且稳定地运行是一个关键问题。Go语言,以其高效的并发处理能力和简洁的语法,成为解决这一问题的理想工具。本文将探讨如何通过Go语言优化微服务架构的性能,包括高效的并发编程、内存管理技巧以及如何利用Go生态系统中的工具来提升服务的响应速度和资源利用率。
|
11天前
|
Rust Linux Go
Rust/Go语言学习
Rust/Go语言学习
|
12天前
|
Java 数据库连接 数据库
携手前行:在Java世界中深入挖掘Hibernate与JPA的协同效应
【8月更文挑战第31天】Java持久化API(JPA)是一种Java规范,为数据库数据持久化提供对象关系映射(ORM)方法。JPA定义了实体类与数据库表的映射及数据查询和事务控制方式,确保不同实现间的兼容性。Hibernate是JPA规范的一种实现,提供了二级缓存、延迟加载等丰富特性,提升应用性能和可维护性。通过结合JPA和Hibernate,开发者能编写符合规范且具有高度可移植性的代码,并利用Hibernate的额外功能优化数据持久化操作。
28 0