❓ Вопрос: Один из ваших товарищей по команде отправил этот код на проверку. Этот код несет потенциальную угрозу. Определите ее и предложите решение для ее устранения.
package main
import (
"fmt"
"time"
)
func main () {
ch := make ( chan int )
go func () {
time.Sleep( 2 * time.Second)
ch <- 42
fmt.Println( "Отправлено: 42" )
}()
val := <-ch
fmt.Println( "Получено:" , val)
fmt.Println( "Продолжение выполнения..." )
}
🌟 На первый взгляд ничего подозрительного в этом коде. Если мы попробуем его запустить, он действительно скомпилируется и запустится без каких-либо заметных проблем.
[Running] go run "main.go"
Отправлено: 42
Получено: 42
Продолжение выполнения...
[Done] exited with code=0 in 2.124 seconds
💡 Сам код тоже кажется в порядке. У нас есть правильно реализованное параллельное потребление с двумя горутинами, работающими независимо. Давайте разберем код и посмотрим, что происходит:
🌟 Канал
ch создан с помощью
make(chan int). Это небуферизованный канал.
🌟 Запускается горутина, которая спит 2 секунды, а затем отправляет значение 42 на канал.
🌟 Основная функция выполняет операцию чтения по ch с помощью
val := <-ch.
🔍 Опять же, кажется, все в порядке. Но на самом деле операция отправки задерживается. Анонимная горутина ждет 2 секунды, прежде чем отправить значение в канал. Поэтому, когда мы запускаем этот код, основная функция начинает считывать канал и ожидает там значение, прежде чем канал будет заполнен значением. Эта операция блокирует дальнейшее выполнение кода.
❗️ Подобная блокировка может вызвать серьезные проблемы с параллелизмом. Если основная горутина (или любая критическая горутина) блокируется на неопределенное время, ожидая данные, это может помешать выполнению других важных задач, что приведет к взаимоблокировкам или неотзывчивому поведению.
💡 Чтобы избежать блокирования чтения, можно использовать неблокирующие альтернативы, такие как оператор
select с вариантом по умолчанию. Оператор
select в Go — это мощная функция, которая позволяет goroutine ожидать несколько операций связи, что позволяет выполнять неблокирующие операции и обрабатывать несколько каналов. Оператор
select работает, оценивая несколько операций канала и продолжая с первой готовой. Если несколько операций готовы, одна из них выбирается случайным образом. Если ни одна операция не готова, выполняется вариант по умолчанию, если он есть, что делает его неблокирующей операцией.
🔍 Вот его синтаксис:
select {
case <-ch1:
// Сделать что-то, когда ch1 готов к приему
case ch2 <- value :
// Сделать что-то, когда ch2 готов к отправке
default :
// Сделать что-то, когда ни один канал не готов (неблокируемый путь)
}
🌟 Теперь исправим изначальную проблему с помощью select:
package main
import (
"fmt"
"time"
)
func main () {
ch := make ( chan int )
// Goroutine для отправки данных в канал через 2 секунды
go func () {
time.Sleep( 2 * time.Second)
ch <- 42
fmt.Println( "Отправлено: 42" )
}()
// Основная функция, выполняющая неблокирующее чтение
for {
select {
case val := <-ch:
fmt.Println( "Получено:" , val)
fmt.Println( "Продолжение выполнения..." )
return
default :
fmt.Println( "Значения не были получены" )
time.Sleep( 500 * time.Millisecond) // Некоторое время ждем, чтобы предотвратить зацикливание
// обрабатываем поток выполнения инструкций и операций, которые должны быть продолжены
}
}
}
@golang_interview