Вышел semaphore 4.0

Вышел semaphore 4.0

Расширен интерфейс, удалена зависимость от context и добавлена консольная утилита для параллельного запуска

Библиотека является имплементацией одноимённого паттерна. Написана на языке Go. Реализация основана на каналах.

Что нового

  • Описание релиза: 4.0.0.
  • Полный список изменений: 3.0.5..4.0.0.
  • Документация по консольной утилите: cmd/semaphore.

Практика показала, что использование пакета context, добавленного в предыдущей версии, оказалось избыточным, поэтому он был заменён на следующие примитивы:

// WithDeadline returns empty struct channel based on Time channel.
func WithDeadline(deadline time.Time) <-chan struct{} {
	// go 1.5 doesn't support time.Until(deadline)
	return WithTimeout(deadline.Sub(time.Now())) //nolint: gosimple
}

// WithSignal returns empty struct channel based on Signal channel.
func WithSignal(s os.Signal) <-chan struct{} {
	ch := make(chan struct{})
	if s == nil {
		close(ch)
		return ch
	}
	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, s)
		<-c
		close(ch)
		signal.Stop(c)
	}()
	return ch
}

// WithTimeout returns empty struct channel based on Time channel.
func WithTimeout(timeout time.Duration) <-chan struct{} {
	ch := make(chan struct{})
	if timeout <= 0 {
		close(ch)
		return ch
	}
	go func() {
		<-time.After(timeout)
		close(ch)
	}()
	return ch
}

Это позволило расширить список поддерживаемых версий компилятора до Go1.5+ без необходимости подключать зависимость от golang.org/x/net/context (сам пакет context появился только в Go1.7).

Побочным эффектом стала бо́льшая гибкость в определении “прерывателя” операции получения ресурса:

sem := semaphore.New(runtime.GOMAXPROCS(0))
interrupter := semaphore.Multiplex(
	semaphore.WithTimeout(time.Second),
	semaphore.WithSignal(os.Interrupt),
)
_, err := sem.Acquire(interrupter)
if err == nil {
	panic("press Cmd+C on Mac or Ctrl+C on Windows")
}
// successful interruption

Функция Multiplex может принимать неограниченное количество “прерывателей”:

// Multiplex combines multiple empty struct channels into one.
func Multiplex(channels ...<-chan struct{}) <-chan struct{} {
	ch := make(chan struct{})
	if len(channels) == 0 {
		close(ch)
		return ch
	}
	go func() {
		cases := make([]reflect.SelectCase, 0, len(channels))
		for _, ch := range channels {
			cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)})
		}
		reflect.Select(cases)
		close(ch)
	}()
	return ch
}

Больше практических примеров

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

type Pool struct {
	sem  semaphore.Semaphore
	work chan func()
}

func (p *Pool) Schedule(task func()) {
	select {
	case p.work <- task: // delay the task to already running workers
	case release, ok := <-p.sem.Signal(nil): if ok { go p.worker(task, release) } // ok is always true in this case
	}
}

func (p *Pool) worker(task func(), release semaphore.ReleaseFunc) {
	defer release()
	var ok bool
	for {
		task()
		task, ok = <-p.work
		if !ok { return }
	}
}

func New(size int) *Pool {
	return &Pool{
		sem:  semaphore.New(size),
		work: make(chan func()),
	}
}

func main() {
	pool := New(2)
	pool.Schedule(func() { /* do some work */ })
	...
	pool.Schedule(func() { /* do some work */ })
}

Вместе с рефакторингом в релиз была добавлена одноимённая консольная утилита, которая позволяет параллельно выполнять другие команды, лимитируя число одновременно запущенных потоков.

Теги

go semaphore

Автор

Камиль Самигуллин
Камиль Самигуллин

Разработчик

В этом релизе semaphore я решил отказаться от использования context, а также добавил одноимённую консольную утилиту для параллельного запуска команд.

Спонсоры

Опубликовано

3.09.2017