Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

死锁,或者锁不是原子性 #24

Closed
molixiaoge opened this issue Jul 29, 2024 · 7 comments · Fixed by #26
Closed

死锁,或者锁不是原子性 #24

molixiaoge opened this issue Jul 29, 2024 · 7 comments · Fixed by #26
Labels
bug Something isn't working

Comments

@molixiaoge
Copy link

molixiaoge commented Jul 29, 2024

开启两个协程调用同一个函数,进行分布式锁测试。

会出现两种预期之外的情况。
1.两个协程同时获得锁

代码如下

package main

import (
	"context"
	"fmt"
	"sync/atomic"
	"time"

	"github.com/go-redis/redis/v8"
	redislock "github.com/jefferyjob/go-redislock"
)

var counter int64

func TestLock(redisClient *redis.Client) {
	ctx := context.Background()

	lock := redislock.New(ctx, redisClient, "lockkey", redislock.WithAutoRenew())

	id := atomic.AddInt64(&counter, 1)

	err := lock.Lock()
	if err != nil {
                fmt.Println("lock failed ", err)
		return
	}

	fmt.Println("lock ", id)
	defer fmt.Println("unlock ", id)

	defer lock.UnLock()

}

func main() {

	counter = 0

	// Create a Redis client
	redisClient := redis.NewClient(&redis.Options{
		Addr:     "192.168.56.102:4000",
		Password: "123",
		DB:       0,
	})

	go func() {
		for {
			fmt.Println(counter)
			time.Sleep(2 * time.Second)
		}
	}()

	for i := 0; i < 2; i++ {
		go func() {

			TestLock(redisClient)

		}()
	}

	fmt.Scanln()
}

输出如下:
1.第一把锁还没解锁,锁了两次

lock  2
lock  1
unlock  2
unlock  1

2.只有一个协程能获得锁

lock  1
lock failed  failed to acquire lock: redis: nil 2
unlock  1
@jefferyjob
Copy link
Owner

您好,您的问题我查看了一下,当第一个协程获取锁之后,第二个锁调用 Lock 会返回 error ,也就是 lock failed failed to acquire lock: redis: nil,这是预期的结果。也就是你提到的2.
然后您讲到的一个第一个锁已经锁定了,但是第二个锁还是能继续锁定。我这边跑了测试,是没有问题的,所以您要么再看看,如果还是有问题,您留言说一声,我看一下

image

@jefferyjob
Copy link
Owner

我觉得这里的 error 可能会对人造成理解上的困难,我应该改一下

@jefferyjob
Copy link
Owner

在您的测试代码中,我觉得应该把解锁代码删除掉,会比较客观,模拟业务一直运行,然后您可以看看另外一把锁会不会枷锁成功

// 注释
// defer fmt.Println("unlock ", id)
// defer lock.UnLock()

@molixiaoge
Copy link
Author

代码进行了更改,TestLock函数调用的时候会一直持有锁不释放,但是还是打印lock两次,只开一个进程多重启几次,则会输出错误结果。

package main

import (
	"context"
	"fmt"
	"sync/atomic"
	"time"

	"github.com/go-redis/redis/v8"
	redislock "github.com/jefferyjob/go-redislock"
)

var counter int64

func TestLock(redisClient *redis.Client) {
	ctx := context.Background()

	lock := redislock.New(ctx, redisClient, "lockkey", redislock.WithAutoRenew())

	id := atomic.AddInt64(&counter, 1)

	err := lock.Lock()
	if err != nil {
		fmt.Println("lock failed ", err)
		return
	}

	fmt.Println("lock ", id)

	for {
		time.Sleep(5 * time.Second)
	}
	// defer fmt.Println("unlock ", id)

	// defer lock.UnLock()

}

func main() {

	counter = 0

	// Create a Redis client
	redisClient := redis.NewClient(&redis.Options{
		Addr:     "192.168.56.102:4000",
		Password: "123",
		DB:       0,
	})

	go func() {
		for {
			fmt.Println(counter)
			time.Sleep(2 * time.Second)
		}
	}()

	for i := 0; i < 2; i++ {
		go func() {

			TestLock(redisClient)

		}()
	}

	fmt.Scanln()
}

输出如下,锁没有释放,lock了两次

0
lock  1
lock  2
2
2

@molixiaoge
Copy link
Author

刚调试了一下,堆栈如下,显示有两把锁

图片

@molixiaoge
Copy link
Author

程序环境

Windows 10
go version go1.21.5 windows/amd64

redis环境

redis:7.2
CentOS Linux release 7.9.2009 (Core)
Linux 102 3.10.0-1160.105.1.el7.x86_64 #1 SMP Thu Dec 7 15:39:45 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

开200个协程,只要有超过一个lock打印就说明有问题

	for i := 0; i < 200; i++ {
		go func() {

			TestLock(redisClient)

		}()
	}

输出

0
time="2024-07-30T19:25:38+08:00" level=info msg="lock 1"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 2"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 23"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 27"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 30"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 34"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 36"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 39"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 37"
time="2024-07-30T19:25:38+08:00" level=info msg="lock 38"
200
200

@jefferyjob
Copy link
Owner

您好,确实存在这样的问题,造成这样问题的原因是因为我当前代码自身实现了一个“可重入锁”,可重入锁自动生成的token是基于time.Now().UnixNano() 纳秒数生成的。
您提供的测试案例中,多个并发中取到的纳秒数是一致的,导致被动选择可重入锁进行加锁计数。
所以我只需要将自动生成的token再加上几位数的随机码就可以解决这个问题,感谢您提出的问题,有问题我们再继续交流!

lock.token = fmt.Sprintf("token_%d", time.Now().UnixNano())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants