当一个goroutine调用这个TryLock方法请求锁时,如果这锁锁没有被其他goroutine持有,那么这个goroutine就会持有这把锁,返回true
当调用TryLock请求锁时,锁已经被其他goroutine持有,直接返回false(也可以设置超时时间)
代码实现
package main
import (
"fmt"
"math/rand"
"sync"
"sync/atomic"
"time"
"unsafe"
)
// 定义Mutex需要的常量(这里直接从Mutex中复制过来的)
// iota只能在const中出现,同行出现,iota值一样
// 每增加一行,iota的值+1
const (
mutexLocked = 1 << iota // 加锁标识位置, 初始 1 << iota(0) --> 1
mutexWoken // 唤醒标识位置, 初始 1 << iota(1) --> 2
mutexStarving // 锁饥饿标识位置, 初始 1 << iota(2) ---> 4
mutexWaiterShift = iota //标识waiter的字起始位置, 初始为iota(3) ---> 3
)
// Mutex 拓展Mutex
type Mutex struct {
sync.Mutex
}
// TryLock 尝试获取锁
func (m *Mutex) TryLock() bool{
// 1.如果成功抢到锁 m.Mutex强转成int32
// 这是一个fast path,如果足够幸运,没有其他goroutine争取这把锁,当前goroutine直接获取锁
if atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.Mutex)), 0, mutexLocked) {
return true
}
// 如果锁处于加锁、唤醒或者锁饥饿状态, 这次请求就不参与竞争了,防止饥饿, 返回false
// 如果锁被其他goroutine持有,返回false
// 如果有其他被唤醒的goroutine来竞争这把锁,返回false ---> 防止其他goroutine饥饿,本次暂不获取
// 如果锁处于饥饿状态,暂时放弃争夺锁,直接返回false
old := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex)))
if old &(mutexLocked | mutexWoken | mutexStarving) != 0{
return false
}
// 尝试在竞争状态下获取锁
new := old | mutexLocked
return atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.Mutex)), old, new)
}
测试
// TryLock测试
// 主goroutine会尝试获取这把锁
// 如果前一个goroutine一秒内释放了这把锁,那么主goroutine就有可能获取这把锁,输出"got the lock"
// 否则也不会阻塞,直接输出"can't get the lock"
func main() {
var mu Mutex
go func() {
mu.Lock()
time.Sleep(time.Duration(rand.Intn(2)) * time.Second)
mu.Unlock()
}()
// 睡眠,让上面的goroutine先执行获取锁
time.Sleep(time.Duration(rand.Intn(2)) * time.Second)
// 尝试获取锁
ok := mu.TryLock()
if ok {
fmt.Println("got the lock")
// 业务逻辑
mu.Unlock()
return
}
// 没有获取到锁
fmt.Println("can't get the lock")
}
Mutex数据结构中,包含两个字段 state和sema, 前四个字节(int32)就是state
Mutex结构中的state字段,标识着锁是否被某个goroutine持有、锁当前是否处于饥饿、是否有等待的goroutine被唤醒、等待者的数量等信息
// state中各标志位的起始位置
// 第一位标记锁是否被持有
// 第二位标记是否有被唤醒的goroutine
// 第三位锁是否处于饥饿
// 第四位向后,锁的等待数量 ---> waiter占了31-3=29位
// 前三位做位运算
// 第四个做位移
const (
mutexLocked = 1 << iota // 加锁标识位置, 初始 1 << iota(0) --> 1
mutexWoken // 唤醒标识位置, 初始 1 << iota(1) --> 2 ---> 10
mutexStarving // 锁饥饿标识位置, 初始 1 << iota(2) ---> 4 ---> 100
mutexWaiterShift = iota // 标识waiter的字起始位置, 初始为iota(3) ---> 3 (只有这个是做位移的)
)
type Mutex struct {
state int32
sema uint32
}
但是state是未暴露的字段,怎么获取 ===> 我们可以使用unsafe的方式获取
// RacerCount 锁的竞争者者的数量
func (m *Mutex) RacerCount() int{
// 获取state字段的值(sync.Mutex前四个字节是state的值)
v := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex)))
// 获取等待者数量 (右移三位)
v = v >> mutexWaiterShift
// 再加上持有者的数量(0或者1)
v = v + (v & mutexLocked)
return int(v)
}
// IsLocked 锁是否被持有
func (m *Mutex) IsLocked() bool {
// sync.Mutex前四个字节是state的值
state := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex)))
return state & mutexLocked == mutexLocked
}
// IsWoken 是否有等待者被唤醒
func (m *Mutex) IsWoken() bool {
// sync.Mutex前四个字节是state的值
state := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex)))
return state & mutexWoken == mutexWoken
}
// IsStarving 锁是否处于饥饿
func (m *Mutex) IsStarving() bool {
// sync.Mutex前四个字节是state的值
state := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex)))
return state & mutexStarving == mutexStarving
}
以上获取的都是瞬时值,高并发情况下,随时会被改变
func tryCount() {
var mu Mutex
for i := 0; i < 1000; i++ {
go func() {
mu.Lock()
time.Sleep(time.Second)
mu.Unlock()
}()
}
time.Sleep(time.Second)
// 输出锁的信息
fmt.Printf("waitings: %d, isLocked: %t, woken: %t, starving: %t\n",
mu.RacerCount(), mu.IsLocked(), mu.IsWoken(), mu.IsStarving())
}
队列我们可以使用Slice实现,但Slice不是线程安全的,出队(Dequeue) 和入队(Enqueue) 会有data race 的问题,我们可以通过Mutex对出队和入队做一个保护
package main
import "sync"
// @File: demo01_my_sychronized_queue
// @Author: [email protected]
// @Date: 2021/9/12 11:56
// @Description:
// 使用mutex实现一个并发安全的队列
// mutex太重了
// 可以使用写时复制技术
// Slice队列
type SliceQueue struct {
data []interface{} // 能存储任何类型的Slice
mu sync.Mutex
}
// NewSliceQueue SliceQueue的构造函数,返回一个SliceQueue的指针对象
func NewSliceQueue(cap int) (q *SliceQueue) {
return &SliceQueue{
data: make([]interface{}, 0, cap),
}
}
// Enqueue 入队, 放在队列的尾部
func (q *SliceQueue) Enqueue(v interface{}) {
// 上锁
q.mu.Lock()
// 加入队列
q.data = append(q.data, v)
// 释放锁
q.mu.Unlock()
}
// Dequeue 出队
func (q *SliceQueue) Dequeue() interface{} {
// 上锁
q.mu.Lock()
// 数据不存在返回nil
if len(q.data) == 0 {
q.mu.Unlock()
return nil
}
// 出队
v := q.data[0]
q.data = q.data[1:]
// 解锁
q.mu.Unlock()
return v
}
func main() {
q := NewSliceQueue(10)
q.Enqueue(1)
q.Enqueue(2)
q.Enqueue(3)
q.Enqueue(4)
fmt.Println(q.Dequeue())
fmt.Println(q.Dequeue())
fmt.Println(q.Dequeue())
fmt.Println(q.Dequeue())
fmt.Println(q.Dequeue())
}