L oop для проверки состояния в параллельной программе - PullRequest
0 голосов
/ 08 января 2020

Я читаю книгу о параллелизме в Go (изучаю сейчас) и нашел этот код:

c  :=  sync.NewCond(&sync.Mutex{})
queue  :=  make([]interface{},  0,  10)
removeFromQueue  :=  func(delay  time.Duration) {
    time.Sleep(delay)
    c.L.Lock()
    queue  =  queue[1:]
    fmt.Println("Removed from queue")
    c.L.Unlock() c.Signal()
} 

for  i  :=  0;  i  <  10;  i++ {
    c.L.Lock()

    // Why this loop?
    for  len(queue)  ==  2 {
        c.Wait()
    }

    fmt.Println("Adding to queue")
    queue  =  append(queue,  struct{}{})
    go  removeFromQueue(1*time.Second)
    c.L.Unlock()
}

Проблема в том, что я не понимаю, почему автор представляет for l oop, отмеченный комментарием. Насколько я вижу, программа была бы правильной без нее, но автор говорит, что l oop есть, потому что Cond будет сигнализировать, что что-то произошло только, но это не означает, что состояние действительно изменилось.

В каком случае это возможно?

1 Ответ

1 голос
/ 08 января 2020

Без реальной книги под рукой, а только с некоторыми фрагментами кода, которые кажутся неконтролируемыми, трудно сказать, что именно имел в виду автор. Но мы можем догадаться. В большинстве языков, в том числе Go, есть общий пункт о условных переменных: ожидание выполнения какого-либо условия требует всего oop в общем . В некоторых конкретных c случаях l oop не требуется.

Документация Go, я думаю, более ясна по этому поводу. В частности, текстовое описание для sync func (c *Cond) Wait() гласит:

Ожидание атомной разблокировки c .L и приостановка выполнения вызывающей программы. После последующего возобновления выполнения, Wait блокирует c .L перед возвратом. В отличие от других систем, Ожидание не может вернуться, если оно не вызвано Широковещанием или Сигналом.

Поскольку c .L не блокируется при первом возобновлении Ожидания , вызывающая сторона, как правило, не может предположить, что условие верно, когда Ожидание возвращается. Вместо этого вызывающий должен подождать в oop:

c.L.Lock()
for !condition() {
    c.Wait()
}
... make use of condition ...
c.L.Unlock()

Я добавил жирный акцент на фразу, которая объясняет причину l oop.

Будь вы может опускать l oop зависит от более чем одной вещи:

  • При каких условиях другая goroutine вызывает Signal и / или Broadcast?
  • Как работает много подпрограмм, и что они могут делать параллельно?

Как сказано в документации Go, есть один случай, о котором не нужно беспокоиться в Go, что мы могли бы в некоторых других системах. В некоторых системах эквивалент Wait иногда возобновляется (через эквивалент Signal), когда Signal (или его эквивалент) фактически не вызывается для условной переменной.

* queue пример, который вы привели, особенно странен, потому что есть только одна процедура - та, в которой выполняется for l oop, которая насчитывает десять, - которая может добавлять записей в очередь. Остальные goroutines только удалить записей. Таким образом, если длина очереди равна 2, и мы приостанавливаем и ожидаем сигнала об изменении длины очереди, длина очереди может измениться только на один или на ноль: никакая другая процедура не может добавить к ней и удалить из него могут только две goroutines, которые мы создали на данный момент. Это означает, что, учитывая этот конкретный пример , мы имеем один из тех случаев, когда l oop в конце концов не требуется.

(Это также странно, что queue дано начальная емкость 10, то есть столько элементов, сколько мы поместим, а затем мы начнем ждать, когда ее длина будет ровно 2, чтобы мы не достигли этой емкости в любом случае. Если бы мы выделили дополнительные программы, которые могли бы добавить в очередь, l oop, который ожидает, пока len(queue) == 2 действительно может быть сигнализирован удалением, которое уменьшает счет со 2 до 1, но не получает шанса возобновить, пока не произойдет вставка, что увеличивает счет до 2. Однако в зависимости от ситуации, l oop может не возобновиться, пока две другие goroutines не добавят запись каждая , например, увеличив счет до 3. Так зачем повторять l oop когда длина равна точно два? Если идея состоит в том, чтобы сохранить слоты очереди, мы должны l oop, пока счетчик больше или равен 2.)

(Быть Помимо всего этого, начальная емкость не имеет значения, поскольку очередь будет динамически изменяться до большого среза, если это необходимо.)

...