Здесь стоит отметить несколько моментов:
goroutines не являются потоками. Не существует «номера goroutine» и не существует фиксированного верхнего предела количества goroutines в системе. 1 Алгоритм Bakery может быть изменен для работы с динамически создаваемыми потоками (используйте список или карту, как в примере Java на странице Википедии), но существует строгое требование уникального идентификатора для «потока», что делает это не очень хорошей идеей для Go. (Вы можете обойти это в свою очередь, используя пакет, который реализует потоковое поведение, включая идентификаторы потоков.)
Как отмечено на странице Википедии:
Алгоритм пекарни Лампорта предполагает последовательную модель памяти согласованности. Немногие, если таковые имеются, языки или многоядерные процессоры реализуют такую модель памяти. Поэтому правильная реализация алгоритма обычно требует вставки ограждений для предотвращения переупорядочения.
Это означает, что вам нужно будет использовать пакет 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(¤tlyServing) < ticket {
runtime.Gosched()
}
return ticket
}
// ExitBakery relinquishes our ticket, allowing the next user to proceed.
func ExitBakery(ticket int64) {
atomic.StoreInt64(¤tlyServing, ticket + 1)
}
Изменение этого параметра для использования двух каналов, так что функция WaitTurn
более эффективен, оставлен в качестве упражнения. (Конечно, нет никаких оснований использовать этот код вообще, кроме как в качестве упражнения.)
1 Вы можете установить лимит времени выполнения, но система будет появляться дополнительные процедуры в любом случае, если вы вызываете какие-либо системные вызовы блокировки. Набор системных вызовов, которые блокируют и когда их вызывают, зависит от времени выполнения, так что вы не можете реально контролировать это, по крайней мере, без написания кода платформы c.