Привет, я пишу Lock с использованием канала, цель которого - заблокировать / разблокировать операцию для данного «приложения».

Общая идея состоит в том, что одна горутина продолжает прослушивать два канала: lockCh и unlockCh. Любая операция Lock() отправляет самодельный канал в lockCh и ожидает чтения из этого самодельного канала; чтение этого канала означает успешное выполнение Lock().

Аналогичный процесс применяется к Unlock().

Для gorouting слушателя он проверяет, заблокировано ли «приложение», когда принимает Lock(), если да, то помещает этот самодельный канал в конец списка официантов. Если кто-то Unlock(), он просыпается (отправляя сообщение на канал) следующего официанта или удаляет список официантов, если никто другой не ожидает блокировки.

Код помещен ниже, я не знаю, где не так, но тестовые примеры просто не проходят (он блокируется после нескольких Lock() и Unlock()!)

Спасибо, что дали мне совет.

type receiver struct {
            app  string
            ch   chan struct{}
            next *receiver
}

type receiveList struct {
            head *receiver
            tail *receiver
}

type AppLock struct {
            lockCh   chan receiver
            unlockCh chan receiver

            // Consider lock x:
            // if map[x] doesn't exist, x is unlocked
            // if map[x] exist but head is nil, x is locked but no waiter
            // if map[x] exist and head isn't nil, x is locked and there're waiters
            m map[string]receiveList
}

func NewAppLock() *AppLock {
            l := new(AppLock)
            l.lockCh = make(chan receiver)
            l.unlockCh = make(chan receiver)
            l.m = make(map[string]receiveList)

            go l.lockRoutine()
            return l
}

func (l *AppLock) Lock(app string) {
            ch := make(chan struct{})
            l.lockCh <- receiver{
                app: app,
                ch:  ch,
            }
            <-ch
}

func (l *AppLock) Unlock(app string) {
            ch := make(chan struct{})
            l.unlockCh <- receiver{
                app: app,
                ch:  ch,
            }
            <-ch
}

func (l *AppLock) lockRoutine() {
            for {
                select {
                case r := <-l.lockCh:
                    rlist, ok := l.m[r.app]
                    if ok { // already locked
                        if rlist.head == nil { // no waiter
                            rlist.head = &r
                            rlist.tail = &r
                        } else { // there're waiters, wait in queue
                            rlist.tail.next = &r
                            rlist.tail = &r
                        }
                    } else { // unlocked
                        rlist = receiveList{}
                        l.m[r.app] = rlist
                        r.ch <- struct{}{}
                    }
                case r := <-l.unlockCh:
                    rlist, ok := l.m[r.app]
                    if ok {
                        if rlist.head == nil { // no waiter
                            delete(l.m, r.app)
                            r.ch <- struct{}{}
                        } else { // there're waiters
                            candidate := rlist.head
                            if rlist.head == rlist.tail {
                                rlist.head = nil
                                rlist.tail = nil
                            } else {
                                rlist.head = rlist.head.next
                            }
                            candidate.ch <- struct{}{}
                            r.ch <- struct{}{}
                        }
                    } else {
                        panic("AppLock: inconsistent lock state")
                    }
                }
            }
}

Контрольная работа:

func main() {
        app := "APP"
        appLock := NewAppLock()
        c := make(chan bool)

        for i := 0; i < 10; i++ {
            go func(l *AppLock, loops int, cdone chan bool) {
                for i := 0; i < loops; i++ {
                    l.Lock(app)
                    l.Unlock(app)
                }
                cdone <- true
            }(appLock, 1, c)
        }

        for i := 0; i < 10; i++ {
            <-c
        }

    fmt.Println("DONE")
}
0
dastan 5 Сен 2016 в 17:19

4 ответа

Лучший ответ

Я только что нашел ошибку в своем коде.

type AppLock struct {
    lockCh   chan receiver
    unlockCh chan receiver

    // Consider lock x:
    // if map[x] doesn't exist, x is unlocked
    // if map[x] exist but head is nil, x is locked but no waiter
    // if map[x] exist and head isn't nil, x is locked and there're waiters
    m map[string]receiveList
}

Первоначально я думал, что head и tail внутри receiveList - все это указатели, поэтому мы всегда можем работать с одним и тем же списком официантов, даже если receiveList не является типом указателя. . (Очевидно, это неправильно)

В самом деле, это нормально, если мы будем читать только из head и tail без использования типа указателя для receiveList. Однако я пишу им внутри lockRoutine, который записывает их копию. Это проблема.

0
dastan 6 Сен 2016 в 11:14

Я думаю, проблема в вашем цикле main for. Вы не закрываете канал c там, где это вызывает тупик. Попробуйте изменить цикл на это:

for i := 0; i < 10; i++ {
    var wg sync.WaitGroup // use WaitGroup
    wg.Add(1)
    go func(l *AppLock, loops int, cdone chan bool) {
        defer wg.Done()
        for i := 0; i < loops; i++ {
            l.Lock(app)
            l.Unlock(app)
        }
        cdone <- true
    }(appLock, 1, c)
    go func() {
        wg.Wait()
        close(c)
    }()
}

Рабочий код: https://play.golang.org/p/cbXj0CPTGO

Хотя это должно решить проблему взаимоблокировки, это не обязательно означает, что программа работает правильно. Чтобы понять это, вам понадобятся более качественные тесты.

-1
abhink 5 Сен 2016 в 15:11

Вам нужно реализовать конечный автомат, содержащий связанный список? Может быть гораздо более простое решение, использующее обычную конкуренцию канала Go для предоставления очереди.

Итак, клиенты выстраиваются в очередь для доступа к небуферизованному каналу. Служба блокировки просто ждет клиента. По прибытии служба блокировки переходит в заблокированное состояние. Разблокировка сигнализируется с использованием небуферизованного канала, и состояние возвращается к разблокированному.

Пока она заблокирована, служба блокировки игнорирует последующие запросы на получение блокировки. Эти клиенты должны ждать (среда выполнения Go предоставляет для этого очередь). Каждый раз, когда он разблокируется, служба может принимать следующего клиента.

type AppLock struct {
    lockCh   chan struct{}
    unlockCh chan struct{}
}

func NewAppLock() *AppLock {
    l := new(AppLock)
    l.lockCh = make(chan struct{})
    l.unlockCh = make(chan struct{})
    go l.lockRoutine()
    return l
}

func (l *AppLock) lockRoutine() {
    for {
        // take the next client off the channel
        <- l.lockCh
        // we are now in a locked state; wait until the client has released
        // which it signals via the channel c
        <- l.unlockCh
    }
}

func (l *AppLock) Lock() {
    // join the queue for a lock
    l.lockCh <- struct{}{}
}

func (l *AppLock) Unlock() {
    l.unlockCh <- struct{}{}
}

И цикл вызова содержит

        for i := 0; i < loops; i++ {
            l.Lock()
            l.Unlock()
        }

Важно, чтобы каналы не были буферизованы, потому что клиенты должны ждать завершения рандеву, прежде чем они узнают, что получили (для l.Lock()) или сняли (для l.Unlock()) блокировку.

https://play.golang.org/p/2j4lkeOR_s

PS

Не могу не задаться вопросом, нужен ли вам RWMutex (https://golang.org/pkg/sync/ #RWMutex)

0
Rick-777 6 Сен 2016 в 15:17

Есть гораздо более простое решение. Буферизованный канал с одним слотом уже функционирует как простой шкафчик. Нет необходимости в подпрограмме менеджера или очереди связанного списка:

// AppLock maintains a single exclusive lock for each app name string provided
type AppLock struct {
    // Consider lock x:
    // if map[x] doesn't exist, x is implicitly unlocked
    // if map[x] exist and can be read from, x is unlocked
    // if map[x] exist and blocks on read, x is locked
    m map[string]chan struct{}

    // RWMutex to protect the map writes, but allow free read access
    mut sync.RWMutex
}

func NewAppLock() *AppLock {
    l := new(AppLock)
    l.m = make(map[string]chan struct{})
    return l
}

func (l *AppLock) Lock(app string) {
    l.mut.RLock()
    ch, ok := l.m[app]
    l.mut.RUnlock()

    if ok {
        <-ch // blocks until lock acquired
    } else {
        l.mut.Lock()
        l.m[app] = make(chan struct{}, 1)
        // we don't want to put anything on it yet, because we still want the lock
        l.mut.Unlock()
    }
}

func (l *AppLock) Unlock(app string) {
    l.mut.RLock()
    ch, ok := l.m[app]
    l.mut.RUnlock()

    if ok {
        select {
        case ch <- struct{}{}:
            // do nothing, expected behavior
        default:
            // something already on channel, which means it wasn't locked
            panic("Unlock called on app not previously locked")
        }
    } else {
        panic("Unlock called on app not previously locked")
    }
}

https://play.golang.org/p/o02BHfRto3

Это поддерживает один канал с буфером 1 для каждого приложения. Чтобы заблокировать, вы пытаетесь читать с канала. Если вы получаете что-то (это просто общий токен, представляющий блокировку), вы получаете блокировку и помещаете токен обратно в канал, чтобы разблокировать его. Если прием блокируется, блокируется что-то еще, и прием разблокируется, когда эта другая подпрограмма разблокируется (возвращает токен в канал).

Кстати, эта система по-прежнему работает как система «первым пришел - первым обслужен», поскольку каналы Go упорядочены и будут заполнять тот прием, который начал получать раньше всех (среди тех, кто все еще пытается получить).

0
Kaedys 9 Сен 2016 в 15:00