Найти тему
ГоГофер

Горутины при больших нагрузках на сервер на Golang (продакшн)

Одна из компаний, занимающихся разработкой программного обеспечения для автоматизации процессов в производственной сфере, столкнулась с проблемой слишком большого количества запросов на сервер, что приводило к перегрузке системы и снижению производительности. Разработчики решили оптимизировать свой код, используя горутины и каналы.

Компания использовала язык golang для написания своего приложения. Приложение работало с большим количеством данных, которые передавались между различными модулями. Для этого были созданы множество горутин и каналов.

Однако, с ростом числа пользователей, количество запросов на сервере также увеличивалось, что приводило к перегрузке системы и снижению производительности. Разработчики понимали, что им необходимо оптимизировать свой код, чтобы обеспечить более эффективную работу приложения.

Для начала они решили использовать пул горутин, чтобы переиспользовать уже существующие горутины для обработки новых запросов. Для этого они создали пакет pool, который содержал в себе пул горутин.

type Pool struct {
  mu     sync.Mutex
  workers   []*Worker
  idleWorkers []*Worker
}
type Worker struct {
  taskChan chan func()
  stopChan chan bool
}

Затем они создали функцию, которая добавляла задачи в пул горутин.

func (p *Pool) AddTask(task func()) {
  p.mu.Lock()
  defer p.mu.Unlock()
  if len(p.idleWorkers) == 0 {
    worker := &Worker{
      taskChan: make(chan func()),
      stopChan: make(chan bool),
    }
    p.workers = append(p.workers, worker)
    go worker.start()
  }
  worker := p.idleWorkers[len(p.idleWorkers)-1]
  p.idleWorkers = p.idleWorkers[:len(p.idleWorkers)-1]
  worker.taskChan <- task
}

Далее разработчики обратили внимание на использование каналов в своем приложении. Они заметили, что некоторые каналы были созданы без необходимости и использовались только для передачи данных между горутинами. Вместо этого они решили использовать буферизованные каналы, которые позволяют хранить данные в очереди и передавать их по мере необходимости.

var (
  bufferLen = 1000
)
func main() {
  ch := make(chan int, bufferLen)
  go producer(ch)
  go consumer(ch)
}
func producer(ch chan<- int) {
  for i := 0; i < bufferLen; i++ {
    ch <- i
  }
  close(ch)
}
func consumer(ch <-chan int) {
  for i := range ch {
    fmt.Println(i)
  }
}

Кроме того, разработчики внесли изменения в свой код, чтобы использовать select оператор, который позволяет выбирать из нескольких каналов одновременно. Это позволило им эффективно управлять множеством каналов и горутин, что улучшило производительность и надежность приложения.

func main() {
  ch1 := make(chan int)
  ch2 := make(chan int)
  go func() {
    for {
      select {
      case n := <-ch1:
        fmt.Println("Received from ch1:", n)
      case n := <-ch2:
        fmt.Println("Received from ch2:", n)
      }
    }
  }()
  ch1 <- 1
  ch2 <- 2
}

В результате всех этих изменений, компания смогла значительно улучшить производительность своего приложения и обеспечить более эффективную работу для своих пользователей. Они поняли, что правильное использование горутин и каналов может существенно повысить производительность и надежность приложения на языке golang.