In the previous article, lock acquisition and unlock were implemented, but lock acquisition is non-blocking, which means that failure to acquire the lock is immediately returned. In some scenarios, we need to keep a task executing only one request, with other requests queued up behind. Just like the company only has one toilet, only one person can be in it at a time, and the others queue outside, but the queue is preemptive, whoever is fast will be first.

Next we implement a blocking lock as follows

// Block blocks the lock
func (lk *lock) Block(expiration time.Duration) bool {

	t := time.Now()

	for {

		cxt, cancel := context.WithTimeout(context.Background(), 3*time.Second)

		ok, err := redis.Client().SetNX(cxt, lk.key, lk.requestId, lk.expiration).Result()

		cancel()

		iferr ! =nil {

			return false
		}

		if ok {

			return true
		}

		time.Sleep(200 * time.Millisecond)

		if time.Now().Sub(t) > expiration {

			return false}}}Copy the code

Add a loop to the non-blocking lock acquisition, and return false if the wait time is exceeded.

Implement a forcible lock release method, which may be used in some scenarios

// ForceRelease forces the lock release, ignoring the request ID
func (lk *lock) ForceRelease(a) error {

	cxt, cancel := context.WithTimeout(context.Background(), 3*time.Second)

	defer cancel()

	_, err := lk.connect.Del(cxt, lk.key).Result()

	return err

}
Copy the code

Call the instance

non-blocking

l := lock.NewLock("test".10*time.Second)

defer l.Release()

if l.Get() {

    return response.Resp().String("Lock taken.")}return response.Resp().String("Failed to get lock")
Copy the code

blocking

l := lock.NewLock("test".10*time.Second)

defer l.Release()

if l.Block(5 * time.Second) {

    return response.Resp().String("Lock taken.")}return response.Resp().String("Failed to get lock")
Copy the code

Source code: github.com/PeterYangs/…