golang 锁

一、未加锁场景

1. 直接而上代码:
package main

import "sync"



func main() {
	for j := 0; j < 3; j++ {
		var wg sync.WaitGroup
		// 变量
		var counter int
		for i := 0; i < 1000; i++ {
			wg.Add(1)
			go func() {
				defer wg.Done()
				counter++
			}()
		}
		wg.Wait()
		println(counter)
	}
}

2. 多次执行结果,count值不定
GOROOT=C:\Go #gosetup
GOPATH=E:\Goworkspace #gosetup
C:\Go\bin\go.exe build -i -o C:\Users\wangyeyu\AppData\Local\Temp\___go_build_main_go__3_.exe E:/Goworkspace/src/GoPro/test/main.go #gosetup
"D:\program\GoLand 2018.1\bin\runnerw.exe" C:\Users\wangyeyu\AppData\Local\Temp\___go_build_main_go__3_.exe #gosetup
966
930
921

Process finished with exit code 0

二、 加锁场景

1. 上代码:
package main

import "sync"

var mtx sync.Mutex

func main() {
	for j := 0; j < 3; j++ {
		var wg sync.WaitGroup
		// 变量
		var counter int
		for i := 0; i < 1000; i++ {
			wg.Add(1)
			go func() {
				defer wg.Done()
				mtx.Lock()
				counter++
				mtx.Unlock()
			}()
		}
		wg.Wait()
		println(counter)
	}
}
2. 多地执行结果相同
GOROOT=C:\Go #gosetup
GOPATH=E:\Goworkspace #gosetup
C:\Go\bin\go.exe build -i -o C:\Users\wangyeyu\AppData\Local\Temp\___go_build_main_go__3_.exe E:/Goworkspace/src/GoPro/test/main.go #gosetup
"D:\program\GoLand 2018.1\bin\runnerw.exe" C:\Users\wangyeyu\AppData\Local\Temp\___go_build_main_go__3_.exe #gosetup
1000
1000
1000

Process finished with exit code 0

三、 trylock

1.实现原理,利用channel读写阻塞的原理,在创建锁的时候给一个钥匙,lock的时候拿走,unlock的时候放入。拿到锁了count+1,否则返回
package main

import "sync"

// Lock try lock
type Lock struct {
	c chan struct{}
}

func NewLock() Lock {
	var l Lock
	l.c = make(chan struct{}, 1)
	l.c <- struct{}{}
	return l
}

func (l Lock) TryLock() bool {
	lockResult := false

	select {
	//channel队列内有钥匙,则可加锁,并取走钥匙
	case <-l.c:
		lockResult = true
	default:
	}
	return lockResult
}

func (l Lock) Unlock() {
	//放入钥匙
	l.c <- struct{}{}
}

// 变量
var counter int

func main() {
	var l = NewLock()
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if !l.TryLock() {
				// log error
				println("lock failed")
				return
			}
			counter++
			println("current counter", counter)
			l.Unlock()
		}()
	}
	wg.Wait()
}
2. 输出结果:多次输出同一count
GOROOT=C:\Go #gosetup
GOPATH=E:\Goworkspace #gosetup
C:\Go\bin\go.exe build -i -o C:\Users\wangyeyu\AppData\Local\Temp\___go_build_main_go__3_.exe E:/Goworkspace/src/GoPro/test/main.go #gosetup
"D:\program\GoLand 2018.1\bin\runnerw.exe" C:\Users\wangyeyu\AppData\Local\Temp\___go_build_main_go__3_.exe #gosetup
1000
1000
1000

Process finished with exit code 0
3. 活锁:上述try lock场景会造成大量goroutine抢锁,CPU被浪费,单机情况不建议使用。

四、 分布式锁-基于redis的setnx

1.代码
package main

import (
	"sync"
	"github.com/go-redis/redis"
	"time"
	"fmt"
)

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			incr()
		}()
		wg.Wait()
	}
}

func incr() {
	client := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	})

	var lockKey = "count_key"
	var countKey = "count"
	resp := client.SetNX(lockKey, 1, time.Second*5)
	lockSuccess, err := resp.Result()
	if err != nil || !lockSuccess {
		fmt.Println(err)
		fmt.Println("lock faild !")
		return
	}
	getResp := client.Get(countKey)
	cValue, err := getResp.Int64()
	if err == nil {
		cValue ++
		resp := client.Set(countKey, cValue, 0)
		_, err := resp.Result()
		if err != nil {
			fmt.Println("set count failed!")
		}
	}
	fmt.Print("current value is ")
	fmt.Println(cValue)
	delResp := client.Del(countKey)
	unLockSuccess, err := delResp.Result()
	if err != nil && unLockSuccess <= 0 {
		fmt.Println(err)
		fmt.Println("unlock failed!")
	}
}
2. setnx
远程调用setnx实际上和单机的trylock非常相似,如果获取锁失败,那么相关的任务逻辑就不应该继续向前执行;

setnx很适合在高并发场景下,用来争抢一些“唯一”的资源。比如交易撮合系统中卖家发起订单,而多个买家会对其进行并发争抢。这种场景我们没有办法依赖具体的时间来判断先后,因为不管是用户设备的时间,还是分布式场景下的各台机器的时间,都是没有办法在合并后保证正确的时序的。哪怕是我们同一个机房的集群,不同的机器的系统时间可能也会有细微的差别。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值