Golang sync.Mutex拓展功能

Mutex 拓展额外功能

TryLock实现

当一个goroutine调用这个TryLock方法请求锁时,如果这锁锁没有被其他goroutine持有,那么这个goroutine就会持有这把锁,返回true

当调用TryLock请求锁时,锁已经被其他goroutine持有,直接返回false(也可以设置超时时间)

Golang sync.Mutex拓展功能_第1张图片

代码实现

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
}

Golang sync.Mutex拓展功能_第2张图片

​ 但是state是未暴露的字段,怎么获取 ===> 我们可以使用unsafe的方式获取

RacerCount
// 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
 // IsLocked 锁是否被持有
func (m *Mutex) IsLocked() bool {
	// sync.Mutex前四个字节是state的值
	state := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex)))

	return state & mutexLocked == mutexLocked
}
IsWoken
// IsWoken 是否有等待者被唤醒
func (m *Mutex) IsWoken() bool {
	// sync.Mutex前四个字节是state的值
	state := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex)))
	
	return state & mutexWoken == mutexWoken
}

IsStarving
// 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())
}
使用Mutex实现一个线程安全的队列

​ 队列我们可以使用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())
}

你可能感兴趣的:(Golang,Golang,sync,Mutex,tryLock)