1 Зачем нужен канал
Горутина — главная новая функция Go, и именно этот большой убийца заставляет Go останавливаться и восхищаться многими прохожими, а верующих ликовать и говорить об этом.
Использование сопрограмм также очень просто: в Go использование ключевого слова «go», за которым следует функция, которая должна быть выполнена, означает, что запускается новая сопрограмма для выполнения кода функции.
func main() {
go test()
fmt.Println("it is the main goroutine")
time.Sleep(time.Second * 1)
}
func test() {
fmt.Println("it is a new goroutine")
}
Можно просто понять, что сопрограмма в Go — это более легкий механизм параллелизма, который поддерживает более высокий параллелизм.
Внимательно посмотрите на основную функцию выше, в которой есть операция, которая спит в течение одной секунды.Если вы удалите эту строку, в результате печати не будет «это новая горутина». Это связано с тем, что новая сопрограмма не успела запуститься, а основная сопрограмма завершается.
Итак, вот вопрос, как мы можем сообщить каждой сопрограмме, закончили ли выполнение друг друга?
Очевидно, мы можем использовать описанный выше метод, чтобы позволить основной сопрограмме заснуть на одну секунду и дождаться, пока вспомогательная сопрограмма удостоверится, что она может быть выполнена. Но как новый язык, он не должен использовать такой низкий способ. Даже старые предшественники Java имеют асинхронный механизм Future и могут блокировать выполнение ожидающих задач с помощью метода get, гарантируя, что состояние выполнения асинхронного процесса может быть известно с первого раза.
Поэтому в Go должно быть что-то особенное, то есть еще одна черта, которая заставляет смотреть на нее прохожих и сводит с ума верующих, — канальность.
2 Как пользоваться каналом
Каналы можно просто рассматривать как коммуникационный мост между сопрограммами и горутинами, которые могут свободно взаимодействовать друг с другом в разных сопрограммах и являются потокобезопасными.
2.1 Классификация каналов
Есть два типа каналов
небуферизованный канал
ch := make(chan string)
буферизованный канал
ch := make(chan string, 2)
2.2 Разница между двумя типами каналов
1. С точки зрения метода объявления имеется буфер с емкостью, то есть следующим числом, где 2 означает, что канал может хранить две переменные типа шеринг
2. Небуферизованный канал сам по себе информацию не хранит.Он отвечает только за переход из рук в руки.Если кто-то передает ему, то он должен передаваться другим.Если есть только операции in или out, то это вызовет блокировку. Буферизованная переменная может хранить переменную заданной емкости, но использование значения, превышающего эту емкость, также приведет к блокировке.
2.3 Примеры использования двух каналов
небуферизованный канал
func main() {
ch := make(chan string)
go func() {
ch <- "send"
}()
fmt.Println(<-ch)
}
Запустите новую сопрограмму в основной сопрограмме, и это анонимная функция, отправьте «отправить» на канал в подпрограмме, и, распечатав результат, мы знаем, что значение, переданное в ch, получено в основном потоке с помощью
Вышеупомянутое заключается в том, чтобы передать значение в канал в подпрограмме и принять значение в основной сопрограмме или наоборот, и значение канала также может быть напечатано нормально.
func main() {
ch := make(chan string)
go func() {
fmt.Println(<-ch)
}()
ch <- "send"
}
буферизованный канал
func main() {
ch := make(chan string, 2)
ch <- "first"
ch <- "second"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Результат выполнения
first
second
Сама структура канала представляет собой очередь «первым пришел — первым обслужен», поэтому порядок вывода здесь такой, как показано в результате.
С точки зрения кода нет необходимости перезапускать горутину, и взаимоблокировка не произойдет (причина будет объяснена позже).
3 Закрытие канала и обход
3.1 Закрыть
Каналы можно закрыть. Синтаксис закрытия небуферизованного и буферизованного каналов одинаков.
close(channelName)
Обратите внимание, что когда канал закрыт, вы не можете пропускать значения на канал, в противном случае будет сообщена ошибка.
func main() {
ch := make(chan string, 2)
ch <- "first"
ch <- "second"
close(ch)
ch <- "third"
}
сообщение об ошибке
panic: send on closed channel
3.2 Обход
Буферизованные каналы обладают пропускной способностью, поэтому их можно обходить, а также поддерживать знакомый обход диапазона.
func main() {
chs := make(chan string, 2)
chs <- "first"
chs <- "second"
for ch := range chs {
fmt.Println(ch)
}
}
Выход
first
second
fatal error: all goroutines are asleep - deadlock!
Правильно, если вы закончите извлекать информацию, хранящуюся в канале, а затем извлечете информацию, он также заблокируется (будет обсуждаться позже).
4-х канальный тупик
Из предыдущего введения мы, вероятно, знаем, что такое канал и как его использовать.
Давайте поговорим о сценарии взаимной блокировки канала и о том, почему он заблокирован (некоторые из них являются моим собственным пониманием, могут быть отклонения, пожалуйста, поправьте меня, если есть какие-либо проблемы).
4.1 Сцена тупика 1
func main() {
ch := make(chan string)
ch <- "channelValue"
}
func main() {
ch := make(chan string)
<-ch
}
В обоих случаях, будь то передача или выборка значения в небуферизованный канал, возникает взаимоблокировка.
Анализ причин
В приведенном выше сценарии используется только одна горутина, основная горутина, и используется небуферизованный канал.
Как упоминалось ранее, небуферизованные каналы не хранят значения, и как передача, так и получение значений будут заблокированы. В случае, когда есть только одна основная сопрограмма, первая часть кода блокируется при передаче значения, а вторая часть кода блокируется при получении значения. Поскольку основная сопрограмма зависла, система ждала, поэтому система оценивает это как взаимоблокировку и, наконец, сообщает об ошибке взаимоблокировки и завершает программу.
продлевать
func main() {
ch := make(chan string)
go func() {
ch <- "send"
}()
}
В этом случае взаимоблокировки не будет.
Некоторые люди говорят, что это происходит из-за того, что основная сопрограмма уходит слишком быстро, а под-сопрограммы не видят машину, поэтому машина уезжает, поэтому она заканчивается без времени на жалобу (тупик).
На самом деле это не так, вот встречный пример
func main() {
ch := make(chan string)
go func() {
ch <- "send"
}()
time.Sleep(time.Second * 3)
}
На этот раз основная сопрограмма ждала вас три секунды, вы должны закончить за три секунды, верно? !
Однако из результата выполнения ни одна подпрограмма не сообщит об ошибке взаимоблокировки, потому что она все время была заблокирована.
Это связано с тем, что, хотя подпрограмма была заблокирована в операторе передачи по значению, это касается только подпрограммы. Основная корутина снаружи все еще что-то делает, через три секунды вы уйдете. Потому что основная сопрограмма закончилась, должна закончиться и подсопрограмма (ведь если не сядете в автобус, то сможете только домой, и световой пестик не поможет)
4.2 Тупик, сцена 2
Сразу после сцены расширения сцены тупика 1 выше мы упомянули, что в сцене расширения нет тупика, потому что основная сопрограмма ушла, поэтому под-сопрограмма может вернуться только домой. То есть они не связаны.
Если эти двое установят соединение через канал, будет ли оно по-прежнему тупиковым?
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
ch2 <- "ch2 value"
ch1 <- "ch1 value"
}()
<- ch1
}
Результат выполнения
fatal error: all goroutines are asleep - deadlock!
Да, это зайдет в тупик.
Анализ причин
Вышеприведенный код не гарантирует, что
Если основная сопрограмма сначала выполнит
Итак, в это время появился ch1 подпрограмм, таких как основная сопрограмма.Подпрограмма ждала получателя ch2. Оператор ch1
Обратный порядок выполнения тот же.
продлевать
Некоторые люди скажут, что я могу избежать взаимоблокировки, изменив этот способ?
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
ch2 <- "ch2 value"
ch1 <- "ch1 value"
}()
<- ch1
<- ch2
}
Нет, результат выполнения по-прежнему тупиковый. Потому что этот порядок все равно не меняет ситуации, когда основная сопрограмма и под-сопрограмма ждут друг друга, то есть условия срабатывания взаимоблокировки.
Измените на следующее, чтобы оно могло закончиться нормально
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
ch2 <- "ch2 value"
ch1 <- "ch1 value"
}()
<- ch2
<- ch1
}
Таким образом, в следующем примере подтверждается, что сцена взаимоблокировки 1 выше связана с тем, что основная сопрограмма не затронута взаимоблокировкой, поэтому она не сообщит об ошибке взаимоблокировки.
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
ch2 <- "ch2 value"
ch1 <- "ch1 value"
}()
go func() {
<- ch1
<- ch2
}()
time.Sleep(time.Second * 2)
}
Мы только что видели, что если
<- ch1
<- ch2
Если его поместить в основную сопрограмму, возникнет взаимоблокировка из-за взаимного ожидания. Но в этом примере тот же код помещается в только что запущенную сопрограмму.Хотя две подпрограммы имеют блокирующие взаимоблокировки, они не повлияют на основную сопрограмму, поэтому выполнение программы не сообщит об ошибке взаимоблокировки.
4.3 Тупик, сцена 3
func main() {
chs := make(chan string, 2)
chs <- "first"
chs <- "second"
for ch := range chs {
fmt.Println(ch)
}
}
Выход
first
second
fatal error: all goroutines are asleep - deadlock!
Анализ причин
Почему происходит взаимоблокировка после вывода всех кэшированных значений канала chs?
На самом деле тоже очень просто.Хотя chs здесь буферизованный канал,но пропускная способность всего два.Когда два выводятся,канал в это время может быть просто эквивалентен небуферизованному каналу.
Очевидно, что для небуферизованного канала простое чтение элементов вызовет блокировку, и она находится в основной сопрограмме, поэтому она эквивалентна тупиковой сцене 1, поэтому она будет тупиковой.
5 Резюме
1. Канал — это мост для связи между сопрограммами
2. Каналы делятся на небуферизованные каналы и буферизованные каналы
3. При использовании канала обращайте внимание на то, не является ли он взаимоблокировкой, и на причины различных взаимоблокировок.