💬 Что из себя представляет пакет semaphore в 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)

}

}