3.21 Go之锁(一)
锁的几种描述
死锁
活锁
饥饿锁
锁的种类
互斥锁
读写互斥锁
读写锁
读锁
写锁
死锁
死锁的概念
两个或者两个以上的进程(或线程),因争夺资源而互相等待.
特点:
这些进程(或线程)都无法推进下去.
此时系统处于死锁的状态或者系统产生了死锁.
这些进程(或线程)称为死锁进程(或线程)
死锁发生的条件
-
互斥条件
线程对于资源的访问是排他的--->一个线程占用某个资源,那么其他线程就必须等待直到该资源被释放
-
请求和保持条件
线程A
占用一个资源a
同时提出占用资源b
的请求.此时线程B
已经占用了资源b
,于是线程A
等待占用资源b
同时又不释放掉已占用的资源a
-
不剥夺条件
线程已获得的资源必须由自己释放,不能被其他线程剥夺
-
环路等待条件
线程A
等到线程B
占用的资源,线程B
在等待线程A
占用的资源,互相不释放自己当前占用的资源于是就有了互相等待
死锁的解决办法
-
约定各线程的顺序
-
使用事务--->注意,在同一事务中,尽可能做到一次锁定获取所需要地资源
-
针对容易产生死锁的业务场景,升级颗粒度,使用表级锁
-
采用分布式事务锁或者乐观锁
示例代码:
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
/*
通过代码设计体验死锁的状态
*/
/* 声明一个结构体,里面包含锁和值 */
type value struct {
// 声明锁
memAccess sync.Mutex
// 声明值
value int
}
/*
main函数中:
1、声明两个变量,这两个变量直接是函数结果
2、开启两个协程
3、进行程序等待
*/
func main() {
// 声明使用的CPU
runtime.GOMAXPROCS(3)
// 声明等待组
var wg sync.WaitGroup
// 声明一个求和变量--->形参是value结构体的指针
sum := func(v1, v2 *value) {
// 结束处理
defer wg.Done()
// v1上锁、休眠
v1.memAccess.Lock()
time.Sleep(2 * time.Second)
// 对v2上锁
v2.memAccess.Lock()
// 打印结果
fmt.Printf("求和的值为:%d\n", v1.value + v2.value)
// 对资源解锁--->遵循先占有的后解锁
v2.memAccess.Unlock()
v1.memAccess.Unlock()
}
// 声明另一个变量,为了造成资源的互相等待所以一开始锁的是另一个资源
product := func(v1, v2 *value) {
// 结束处理
defer wg.Done()
// 上一个变量先锁定v1,所以该变量先锁定v2
v2.memAccess.Lock()
// 休眠2s
time.Sleep(2 * time.Second)
// 尝试锁定v1
v1.memAccess.Lock()
// 打印结果
fmt.Printf("累乘的值为:%d\n", v1.value * v2.value)
// 对资源进行解锁操作,先锁定的后解锁
v1.memAccess.Unlock()
v2.memAccess.Unlock()
}
// 声明变量,通过协程调用这两个函数
var v1, v2 value
// 结构体赋值
v1.value = 1
v2.