当前位置: 首页 > news >正文

GO基础进阶篇 (九)、临界资源安全问题(锁、channel)

临界资源安全问题

在并发编程中对临界资源的处理不当,往往会导致数据的不一致问题

package mainimport ("fmt""time"
)func main() {a := 1go func() {a = 2fmt.Println("goroutine", a)}()a = 3fmt.Println("a", a)time.Sleep(time.Second * 3)fmt.Println("a1", a)//结果//a 3//goroutine 2//a1 2
}

1.售票问题

火车票售票程序。共有10张票,4个售票口同时出售,如何确保库存正常

package mainimport ("fmt""time"
)var ticket int = 10func main() {go sale("售票口1")go sale("售票口2")go sale("售票口3")go sale("售票口4")time.Sleep(time.Second * 3)//售票口2 当前剩余: 10//售票口3 当前剩余: 9//售票口4 当前剩余: 8//售票口1 当前剩余: 10//售票口4 当前剩余: 6//售票口1 当前剩余: 6//售票口2 当前剩余: 4//售票口3 当前剩余: 4//售票口2 当前剩余: 2//售票口3 当前剩余: 2//卖光了//售票口1 当前剩余: 2//卖光了//售票口4 当前剩余: 2//卖光了//售票口2 当前剩余: -2//卖光了
}func sale(name string) {for {if ticket > 0 {time.Sleep(time.Millisecond * 500)fmt.Println(name, "当前剩余:", ticket)ticket--} else {fmt.Println("卖光了")break}}
}

多个线程争抢时会出现问题。

2.mutex锁

sync 包提供了对互斥锁(Mutex)的支持,用于实现多个 goroutines 之间的互斥访问。互斥锁是一种同步原语,可以确保在任何时刻,只有一个 goroutine 能够访问共享资源。

package mainimport ("fmt""sync""time"
)var ticket int = 10
var mutex = sync.Mutex{}func main() {go sale("售票口1")go sale("售票口2")go sale("售票口3")go sale("售票口4")time.Sleep(time.Second * 8)//售票口1 当前剩余: 10//售票口1 当前剩余: 9//售票口4 当前剩余: 8//售票口2 当前剩余: 7//售票口3 当前剩余: 6//售票口1 当前剩余: 5//售票口4 当前剩余: 4//售票口2 当前剩余: 3//售票口3 当前剩余: 2//售票口1 当前剩余: 1//卖光了//卖光了//卖光了//卖光了
}func sale(name string) {for {mutex.Lock()if ticket > 0 {time.Sleep(time.Millisecond * 500)fmt.Println(name, "当前剩余:", ticket)ticket--} else {mutex.Unlock()fmt.Println("卖光了")break}mutex.Unlock()}
}

但是实际上,在GO语言的并发编程中,有一句经单的话:不要以共享内存的方式去通信,而要以通信的方式去共享内存。
在GO语言中,并不鼓励用锁的机制来保护共享状态,在不同的Goroutine中分享信息(以共享内存来通信)。而是鼓励通过channel将共享状态或共享状态的变化在各个Goroutine中之间传递(以通信的方式共享内存)。这样同样能像锁一样,保证同一时间只有一个Goroutine能访问共享状态。

3. WaitGroup

在上面的例子中,我们通过time.sleep()来让主线程等待。这个时间我们不能精准控制。而sync.WaitGroup(通常缩写为 wg)是一种用于等待一组 goroutines 完成执行的同步原语。WaitGroup 通过一个计数器来实现等待,计数器的初始值为 0。每当启动一个新的 goroutine 时,计数器就会递增。当 goroutine 完成时,就会调用 Done 方法将计数器递减。主程序可以调用 Wait 方法来阻塞,直到计数器减至零,表示所有的 goroutines 都已经执行完成。

package mainimport ("fmt""sync""time"
)var ticket int = 10
var mutex = sync.Mutex{}
var wg sync.WaitGroupfunc main() {wg.Add(4)go sale("售票口1")go sale("售票口2")go sale("售票口3")go sale("售票口4")wg.Wait()//售票口1 当前剩余: 10//售票口1 当前剩余: 9//售票口4 当前剩余: 8//售票口2 当前剩余: 7//售票口3 当前剩余: 6//售票口1 当前剩余: 5//售票口4 当前剩余: 4//售票口2 当前剩余: 3//售票口3 当前剩余: 2//售票口1 当前剩余: 1//卖光了//卖光了//卖光了//卖光了
}func sale(name string) {for {mutex.Lock()if ticket > 0 {time.Sleep(time.Millisecond * 500)fmt.Println(name, "当前剩余:", ticket)ticket--} else {mutex.Unlock()wg.Done()fmt.Println("卖光了")break}mutex.Unlock()}
}

4.channel

通道(Channel)是用于在 goroutines 之间进行通信的一种机制。通道提供了一种安全的数据传输方式,确保数据在发送和接收的过程中不会被竞争条件破坏。通道的主要目的是协调不同 goroutines 之间的执行。

package mainimport "fmt"func main() {var ch chan boolch = make(chan bool)go func() {for i := 0; i < 10; i++ {fmt.Println(i)}ch <- true}()data := <-chfmt.Println("通道里的值", data)
}

一个通道发送和接收数据,默认是阻塞的。当一个数据被发送到通道时,在发送语句中被阻塞,直到另一个Goroutine从该通道读取数据。
相对地,当从通道读取数据时,读取被阻塞,直到一个Goroutine将数据写入该通道。本身channel就是同步的,意味着同一时间,只能有一条goroutine来操作。
最后:通道是goroutine之间的连接,所以通道的发送和接收必须处在不同的goroutine中。
这些通道的特性是帮助Goroutines有效地进行通信,而无需像使用其他编程语言中非常常见的显式锁或条件变量。

死锁

如果创建了chan,没有Goroutine来使用了,则会出现死锁。
使用通道时要考虑的一一个重要因素是死锁。如果Goroutine在一个通道上发送数据,那么预计其他的Goroutine应该接收数据。如果这种情况不发生,那么程序将在运行时出现死锁。
类似地,如果Goroutine正在等待从通道接收数据,那么另一些Goroutine将会在该通道上写入数据,否则程序将会死锁。

售票问题的channel实现写法
package mainimport ("fmt""sync""time"
)var ticket int = 10
var wg sync.WaitGroupfunc main() {ticketCh := make(chan int)wg.Add(4)go sale("售票口1", ticketCh)go sale("售票口2", ticketCh)go sale("售票口3", ticketCh)go sale("售票口4", ticketCh)for {time.Sleep(time.Millisecond * 500)ticketCh <- ticketif ticket == 0 {break}}close(ticketCh)fmt.Println(ticket)   //0wg.Wait()fmt.Println(ticket)    //0
}func sale(name string, ticketCh chan int) {for {v, _ := <-ticketChfmt.Println(name, "当前剩余:", v)if v == 0 {wg.Done()break}ticket--}
}
http://www.lryc.cn/news/271386.html

相关文章:

  • Python基础-04(比较运算符、逻辑运算符)
  • MySQL 四种插入命令及其特点与锁机制
  • AKShare学习笔记
  • A星寻路算法
  • QDialog
  • Spark中使用DataFrame进行数据转换和操作
  • windows11新装机,简单评测系统自带软件(基本涵盖日常所需应用)
  • 概念解析 | Shapley值及其在深度学习中的应用
  • ajax的完整写法——success/error/complete+then/catch/done+设置请求头两种方法——基础积累
  • 《Linux详解:深入探讨计算机基础》
  • HarmonyOS 实践之应用状态变量共享
  • ThreadLocal共享变量
  • 前端crypto-js 库: MD5
  • 2024新年快乐
  • OpenCV-Python(21):轮廓特征及周长、面积凸包检测和形状近似
  • 连接progressql报错Cannot load JDBC driver class ‘org.postgresql.Driver‘,亲测有效!!!
  • SQLAlchemy快速入门
  • java 纯代码导出pdf合并单元格
  • Linux自己的应用商店yum
  • 集成电路模拟设计——【基于Serdes 应用的 串化/解串器 时钟与数据恢复电路CDR】
  • OpenWrt 编译入门(小白版)
  • 嵌入式视频播放器(mplayer)
  • 对房价数据集进行处理和数据分析
  • BERT的学习
  • 数据结构OJ实验9-图存储结构和遍历
  • 20231226在Firefly的AIO-3399J开发板上在Android11下调通后摄像头ov13850
  • 0101包冲突导致安装docker失败-docker-云原生
  • 【力扣100】17.电话号码的字母组合
  • 2023。
  • 出现 Cause: java.sql.SQLException: Field ‘id‘ doesn‘t have a default value解决方法