Golang спящий поток вместо занятого ожидания - PullRequest
0 голосов
/ 23 января 2020

Я пишу Go реализацию Lesl ie Алгоритм пекарни Лампорта , который имеет ожидание занято-вращение для обработки максимального количества потоков.


Я пишу go функцию, которая не должна выполняться, пока не будет выполнено специальное условие. Пока мой код выглядит так:

func acquireLock() {
    ...
    for specialConditionIsFalse {
    }
    ...
}

Есть ли более эффективный способ прекратить работу с этим потоком?

Ответы [ 2 ]

5 голосов
/ 23 января 2020

Здесь стоит отметить несколько моментов:

  1. goroutines не являются потоками. Не существует «номера goroutine» и не существует фиксированного верхнего предела количества goroutines в системе. 1 Алгоритм Bakery может быть изменен для работы с динамически создаваемыми потоками (используйте список или карту, как в примере Java на странице Википедии), но существует строгое требование уникального идентификатора для «потока», что делает это не очень хорошей идеей для Go. (Вы можете обойти это в свою очередь, используя пакет, который реализует потоковое поведение, включая идентификаторы потоков.)

  2. Как отмечено на странице Википедии:

    Алгоритм пекарни Лампорта предполагает последовательную модель памяти согласованности. Немногие, если таковые имеются, языки или многоядерные процессоры реализуют такую ​​модель памяти. Поэтому правильная реализация алгоритма обычно требует вставки ограждений для предотвращения переупорядочения.

    Это означает, что вам нужно будет использовать пакет sync/atomic, что отрицательно сказывается на цели написания собственной блокировки.

С этими двумя огромными предостережениями вы можете либо вызвать runtime.Gosched(), где вы бы вызвали функцию yield() стиля POSIX, либо вы можете использовать канал, чтобы сигнализировать, что кто-то "покинул пекарню". "и, следовательно, это следующий ход пользователя. Но сами каналы делают все необходимое взаимное исключение. Упрощенный Go -специфический c алгоритм хлебопечения без Лампорта тривиален (но все нижеприведенное не проверено):

var takeANumber chan int64
var currentlyServing int64

init() {
    takeANumber = make(chan int64)
    go giveNumbers()
}

// giveNumbers hands out ever-increasing ticket numbers
func giveNumbers() {
    for int64 i := 0;; i++ {
        takeANumber <- i
    }
}

// WaitTurn gets a ticket, then waits until it is our turn.  You can
// call this "Lock" if you like.
func WaitTurn() int64 {
    ticket := <-takeANumber
    for atomic.LoadInt64(&currentlyServing) < ticket {
        runtime.Gosched()
    }
    return ticket
}

// ExitBakery relinquishes our ticket, allowing the next user to proceed.
func ExitBakery(ticket int64) {
    atomic.StoreInt64(&currentlyServing, ticket + 1)
}

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


1 Вы можете установить лимит времени выполнения, но система будет появляться дополнительные процедуры в любом случае, если вы вызываете какие-либо системные вызовы блокировки. Набор системных вызовов, которые блокируют и когда их вызывают, зависит от времени выполнения, так что вы не можете реально контролировать это, по крайней мере, без написания кода платформы c.

0 голосов
/ 23 января 2020

Начиная с текущей версии, подпрограммы не являются приоритетными. Это означает, что если у вас есть goroutine с узким l oop, эта goroutine не уступит поток, на котором она работает, другим goroutines. Иногда это может означать, что никакие другие программы никогда не будут запущены.

Вместо ожидания «занято», как это, используйте канал:

<-specialCondition
// Do stuff

и закройте его, когда произойдет специальное условие.

Вы также можете попробовать использовать sync.Cond, но вы можете делать все, что делает условная переменная, используя канал.

...