суббота, 2 ноября 2019 г.

Паттерны в Golang: Семафор

Семафор это синхронизационный паттерн/примитив, который накладывает взаимное исключение на ограниченное количество ресурсов.

Реализация

package semaphore

var (
  ErrNoTickets      = errors.New("не могу захватить семафор")
  ErrIllegalRelease = errors.New("не могу освободить семафор, не захватив его сначала")
)

// Interface содержит поведение семафора, 
// который может быть захвачен (Acquire) 
// и/или освобожден (Release).
type Interface interface {
  Acquire() error
  Release() error
}

type implementation struct {
  sem     chan struct{}
  timeout time.Duration
}

func (s *implementation) Acquire() error {
  select {
  case s.sem <- struct{}{}:
    return nil
  case <-time.After(s.timeout):
    return ErrNoTickets
  }
}

func (s *implementation) Release() error {
  select {
  case _ = <-s.sem:
    return nil
  case <-time.After(s.timeout):
    return ErrIllegalRelease
  }
}

func New(tickets int, timeout time.Duration) Interface {
  return &implementation{
    sem:     make(chan struct{}, tickets),
    timeout: timeout,
  }
}

Данная реализация основана на использовании небуферизованного канала sem - при выполнении функции Acquire в канал отправляется пустая структура и ввиду того что канал не имеет буфера больше данные не могут быть отправлены в канал пока не будут получены из него, поэтому повторный вызов Acquire до вызова Release не даст результата и после истечения таймаута выдаст ошибку ErrNoTickets. При вызове функции Release происходит чтение из канала, после чего в него можно снова выполнять запись, поэтому семафор снова может быть захвачен при вызове Acquire. Также в реализации показана работа оператора select, который блокируется до тех пор, пока один из его блоков case не будет готов к запуску, а затем выполняет этот блок.

Использование

Семафор с таймаутом

tickets, timeout := 1, 3*time.Second
s := semaphore.New(tickets, timeout)

if err := s.Acquire(); err != nil {
    panic(err)
}

// Выполняем важную работу

if err := s.Release(); err != nil {
    panic(err)
}

Запустить пример в песочнице play.golang.org

Семафор без таймаутов

tickets, timeout := 0, 0
s := semaphore.New(tickets, timeout)

if err := s.Acquire(); err != nil {
    if err != semaphore.ErrNoTickets {
        panic(err)
    }

    // Билетов не осталось, не могу работать
    os.Exit(1)
}

Запустить пример в песочнице play.golang.org


Читайте также:


Комментариев нет:

Отправить комментарий