💬 Что из себя представляет пакет semaphore в Go?
Семафор — это конструкция, которая может ограничивать или контролировать доступ к общему ресурсу. В контексте Go, семафор может ограничить доступ горутин к общему ресурсу, но первоначально семафоры использовались для ограничения доступа к потокам.
📌 Немного практики
Семафоры могут иметь веса, которые задают максимальное количество потоков или горутин, получающих доступ к ресурсу.
Процесс поддерживается с помощью методов
Второй параметр
Эта переменная определяет максимальное количество горутин, которые могут быть выполнены данной программой.
Здесь мы определяем семафор с весом, идентичным максимальному количеству горутин, которые могут выполняться одновременно. Это означает, что получать семафор одновременно могут не более чем
Функция
Считываем количество заданий, которые хотим запустить.
Получаем семафор столько раз, сколько заданий определено
Запускаем горутины, которые выполняют эту задачу, и записываем результаты в срез
Получаем все токены таким образом, чтобы вызов
Семафор — это конструкция, которая может ограничивать или контролировать доступ к общему ресурсу. В контексте Go, семафор может ограничить доступ горутин к общему ресурсу, но первоначально семафоры использовались для ограничения доступа к потокам.
📌 Немного практики
Семафоры могут иметь веса, которые задают максимальное количество потоков или горутин, получающих доступ к ресурсу.
Процесс поддерживается с помощью методов
Acquire()
и Release()
, определенных следующим образом:func (s *Weighted) Acquire(ctx context.Context, n int64) error
func (s *Weighted) Release(n int64)
Второй параметр
Acquire()
определяет вес семафора. package main
import (
"context"
"fmt"
"os"
"strconv"
"time"
"golang.org/x/sync/semaphore"
)
var Workers = 4
Эта переменная определяет максимальное количество горутин, которые могут быть выполнены данной программой.
var sem = semaphore.NewWeighted(int64(Workers))
Здесь мы определяем семафор с весом, идентичным максимальному количеству горутин, которые могут выполняться одновременно. Это означает, что получать семафор одновременно могут не более чем
Workers
горутин.func worker(n int) int {
square := n * n
time.Sleep(time.Second)
return square
}
Функция
worker()
выполняется как часть горутины. Однако поскольку мы используем семафор, нет необходимости возвращать результаты в канал.func main() {
if len(os.Args) != 2 {
fmt.Println("Need #jobs!")
return
}
nJobs, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println(err)
return
}
Считываем количество заданий, которые хотим запустить.
// где хранить результаты
var results = make([]int, nJobs)
// требуется для Acquire()
ctx := context.TODO()
for i := range results {
err = sem.Acquire(ctx, 1)
if err != nil {
fmt.Println("Cannot acquire semaphore:", err)
break
}
Получаем семафор столько раз, сколько заданий определено
nJobs
. Если nJobs
больше, чем Workers
, то вызов Acquire()
будет заблокирован и дождется вызовов Release()
для разблокировки.go func(i int) {
defer sem.Release(1)
temp := worker(i)
results[i] = temp
}(i)
}
Запускаем горутины, которые выполняют эту задачу, и записываем результаты в срез
results
. Поскольку каждая горутина записывает данные в свой элемент среза, никаких race condition нет.err = sem.Acquire(ctx, int64(Workers))
if err != nil {
fmt.Println(err)
}
Получаем все токены таким образом, чтобы вызов
sem.Acquire()
блокировался до тех пор, пока все рабочие процесссы/горутины не завершат работу. Функционально это похоже на вызов Wait()
.for k, v := range results {
fmt.Println(k, "->", v)
}
}