func (m *multiLock) Lock(ctx context.Context) error {
	waitFn, err := m.TryLock()
	if err != nil {
		return err
	}
	if waitFn == nil {
		return nil
	}
	return waitFn(ctx)
}
Lock will return nil if a lock was acquired or errQueueFull if there is no more room
in the queue. If the context is canceled while waiting in the queue ctx.Err() may be
returned.

The lock is valid beyond the lifecyle of the provided context, if nil is returned
Unlock must be called to release it.

The number of locks held is guaranteed to be >0 after calling Lock.
package app
import (
	"container/list"
	"context"
	"sync"
	"github.com/pkg/errors"
)
var errQueueFull = errors.New("queue full")
type multiLock struct {
	queue *list.List
	lockCount int
	maxLock   int
	maxWait   int
	lock     chan struct{}
	lockFull chan struct{}
}
func newMultiLock(maxLock, maxWait int) *multiLock {
	m := &multiLock{
		maxLock:  maxLock,
		maxWait:  maxWait,
		queue:    list.New(),
		lock:     make(chan struct{}, 1),
		lockFull: make(chan struct{}, 1),
	}
	m.lock <- struct{}{}
	return m
}
func (m *multiLock) TryLock() (waitFn func(context.Context) error, err error) {
	select {
	case <-m.lock:
		m.lockCount++
		if m.lockCount == m.maxLock {
			m.lockFull <- struct{}{}
		} else {
			m.lock <- struct{}{}
		}
		return nil, nil
	case <-m.lockFull:
	}
	if m.queue.Len() == m.maxWait {
		m.lockFull <- struct{}{}
		return nil, errQueueFull
	}
	lockCh := make(chan struct{})
	e := m.queue.PushBack(lockCh)
	m.lockFull <- struct{}{}
	return func(ctx context.Context) error {
		select {
		case <-lockCh:
			return nil
		case <-ctx.Done():
		}
		select {
		case <-lockCh:
			return nil
		case <-m.lockFull:
		}
		m.queue.Remove(e)
		m.lockFull <- struct{}{}
		return ctx.Err()
	}, nil
}