1. Тип канала
  2. блокировка
  3. Буферизованные каналы
  4. Диапазон
  5. select
    1. timeout
  6. Таймер и тикер
  7. close
  8. Синхронизация
  9. Ссылки

Канал — это основной тип в Go. Его можно представить как трубу, через которую конкурентные единицы выполнения могут отправлять или получать данные для взаимодействия.

Его оператор — стрелка<- 。

ch <- v    // 发送值v到Channel ch中
v := <-ch  // 从Channel ch中接收数据,并将数据赋值给v

(Направление стрелки указывает поток данных)

Как и типы данных map и slice, канал должен быть создан перед использованием:

ch := make(chan int)

Тип канала

Формат определения типа канала следующий:

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

Он включает три типа определений. Необязательный<-обозначает направление канала. Если направление не указано, канал является двунаправленным, способным как принимать, так и отправлять данные.

chan T          // 可以接收和发送类型为 T 的数据
chan<- float64  // 只可以用来发送 float64 类型的数据
<-chan int      // 只可以用来接收 int 类型的数据

<-Он всегда привязывается сначала к крайнему левому типу.

chan<- chan int    // 等价 chan<- (chan int)
chan<- <-chan int  // 等价 chan<- (<-chan int)
<-chan <-chan int  // 等价 <-chan (<-chan int)
chan (<-chan int)

ИспользованиеmakeИнициализируйте канал, и вы можете задать его вместимость:

make(chan int, 100)

Вместимость представляет собой максимальное количество элементов, которое может содержать канал, указывая размер буфера канала.
Если вместимость не задана или установлена в 0, это означает, что канал не имеет буфера, и обмен данными будет происходить (блокировка) только тогда, когда и отправитель, и получатель готовы. Если буфер задан, блокировка может не происходить: отправка блокируется только после заполнения буфера, а получение блокируется только после опустошения буфера. Нулевой канал никогда не обменивается данными.

Встроеннаяcloseметод может закрыть канал.

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

Канал может действовать как очередь «первым пришёл — первым ушёл» (FIFO), где порядок полученных данных соответствует порядку отправленных данных.

Получение из канала поддерживаетмножественное присваивание, например

v, ok := <-ch

Его можно использовать для проверки, был ли канал закрыт.

  1. инструкция отправки
    Инструкция отправки используется для отправки данных в канал, напримерch <- 3。
    Её определение выглядит следующим образом:
SendStmt = Channel "<-" Expression .
Channel  = Expression .

Перед началом обмена данными сначала должны быть вычислены канал и выражение. Например, в следующем примере (3+4) сначала вычисляется как 7, а затем отправляется в канал.

c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)

Прежде чем отправка сможет продолжиться, связь блокируется. Как упоминалось ранее, для небуферизованного канала отправка выполняется только после того, как получатель готов. Если есть буфер и он не заполнен, отправка будет выполнена.

Отправка данных в канал, который уже был закрыт, вызоветпанику во время выполнения。

Отправка данных в nil-канал всегда будет блокироваться.

  1. оператор получения
    <-chОн используется для получения данных из канала ch. Это выражение будет блокироваться до тех пор, пока не появятся данные для получения.

Получение данных из nil-канала всегда будет блокироваться.

Получение данных из закрытого канала не блокируется; оно возвращается немедленно. После получения всех отправленных данных оно возвращает нулевое значение типа элемента.

Как упоминалось ранее, можно использовать дополнительный возвращаемый параметр, чтобы проверить, закрыт ли канал.

x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch

Если OK имеет значение false, это указывает, что полученный x является сгенерированным нулевым значением, и этот канал был закрыт или пуст.

блокировка

По умолчанию отправка и получение остаются заблокированными до тех пор, пока другая сторона не будет готова. Этот метод можно использовать для синхронизации между goroutines без необходимости в явных блокировках или переменных условий.

Как в официальном примере,x, y := <-c, <-cЭта строка будет блокироваться до тех пор, пока результат вычисления не будет отправлен в канал.

import "fmt"
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to c
}
func main() {
    s := []int{7, 2, 8, -9, 4, 0}
    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c
    fmt.Println(x, y, x+y)
}

Буферизованные каналы

Второй параметр make задаёт размер буфера:ch := make(chan int, 100)。

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


Диапазон

for …… rangeИнструкция range может обрабатывать каналы.

func main() {
    go func() {
        time.Sleep(1 * time.Hour)
    }()
    c := make(chan int)
    go func() {
        for i := 0; i < 10; i = i + 1 {
            c <- i
        }
        close(c)
    }()
    for i := range c {
        fmt.Println(i)
    }
    fmt.Println("Finished")
}

range cСгенерированные значения итерации — это значения, отправленные в канал. Итерация будет продолжаться, пока канал не будет закрыт. В приведённом выше примере, если выclose(c)закомментируете его, программа навсегда заблокируется наfor …… rangeэтой строке.


select

selectИнструкция select выбирает из набора возможных операций отправки и получения, которые нужно обработать. Она похожа наswitch, но она используется только для обработки операций обмена.
В нейcaseможет быть оператор отправки, или оператор получения, илиdefault。

receiveоператор может присвоить значение одной или двум переменным. Это должна быть операция получения.

Не более одногоdefault case, его можно разместить в любом месте списка case, хотя мы обычно помещаем его в конец.

import "fmt"
func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

Если нужно одновременно обработать несколько case, например, несколько каналов могут принимать данные, то Go псевдослучайно выберет case для обработки. Если ни один case не нужно обрабатывать, он выберетdefaultдля обработки, еслиdefault caseсуществует. Если нетdefault case, затемselectоператор будет блокироваться до тех пор, пока не появится case, требующий обработки.

Следует отметить, что операции с nil-каналами всегда блокируются. Если нет default case, select только с nil-каналами будет заблокирован навсегда.

selectОператор select и оператор switchswitchпохожи; это не цикл, он выбирает только один case для обработки. Если вы хотите продолжать обрабатывать каналы, можно добавить вокруг него бесконечный цикл for:

for {
    select {
    case c <- x:
        x, y = y, x+y
    case <-quit:
        fmt.Println("quit")
        return
    }
}

timeout

selectОчень важное применение — обработка тайм-аутов. Поскольку, как упоминалось выше, если ни один case не требует обработки, оператор select будет блокироваться навсегда. В этом случае нам может понадобиться операция тайм-аута для обработки ситуаций тайм-аута.
В следующем примере мы отправляем часть данных в канал c1 через 2 секунды, ноselectустановлен тайм-аут в 1 секунду, поэтому мы выведемtimeout 1, вместоresult 1。

import "time"
import "fmt"
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

На самом деле, он используетtime.Afterметод, который возвращает канал типа<-chan Timeоднонаправленного канала, который отправляет текущее время в возвращённый канал в указанное время.


Таймер и тикер

Давайте рассмотрим два канала, связанных со временем.
Timer — это таймер, который представляет одно будущее событие. Вы можете указать таймеру, как долго ждать; он предоставляет канал, и в этот будущий момент канал предоставляет значение времени. В примере ниже вторая строка заблокируется примерно на 2 секунды и не продолжит выполнение, пока не наступит это время.

timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")

Конечно, если вы просто хотите подождать, вы можете использоватьtime.Sleepчтобы добиться этого.

Вы также можете использоватьtimer.Stopчтобы остановить таймер.

timer2 := time.NewTimer(time.Second)
go func() {
    <-timer2.C
    fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
    fmt.Println("Timer 2 stopped")
}

tickerTicker — это таймер, который срабатывает периодически. Он отправляет событие (текущее время) в канал с интервалом, и получатель канала может читать события из канала с фиксированными интервалами. В примере ниже тикер срабатывает каждые 500 миллисекунд; вы можете наблюдать моменты вывода.

ticker := time.NewTicker(time.Millisecond * 500)
go func() {
    for t := range ticker.C {
        fmt.Println("Tick at", t)
    }
}()

Подобно таймеру, тикер также может использоватьStopметод для остановки. После остановки получатели больше не будут получать данные из канала.

close

Встроенный метод close можно использовать для закрытия канала.

Давайте обобщим операции отправителей и получателей после закрытия канала.
Если канал c был закрыт, продолжение отправки в него данных приведёт кpanic: send on closed channel:

import "time"
func main() {
    go func() {
        time.Sleep(time.Hour)
    }()
    c := make(chan int, 10)
    c <- 1
    c <- 2
    close(c)
    c <- 3
}

Но из этого закрытого канала можно не только прочитать уже отправленные данные, но и постоянно читать нулевые значения:

c := make(chan int, 10)
c <- 1
c <- 2
close(c)
fmt.Println(<-c) //1
fmt.Println(<-c) //2
fmt.Println(<-c) //0
fmt.Println(<-c) //0

Но если использоватьrangeдля чтения, цикл for прервётся после закрытия канала:

c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
    fmt.Println(i)
}

Используяi, ok := <-cможно проверить состояние канала, чтобы определить, является ли значение нулевым или нормально прочитанным.

c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false

Синхронизация

Каналы можно использовать для синхронизации между горутинами.
В следующем примере главная горутина ожидает завершения задачи воркером через канал done. После завершения задачи воркеру нужно лишь отправить порцию данных в канал, чтобы уведомить главную горутину о завершении задачи.

import (
    "fmt"
    "time"
)
func worker(done chan bool) {
    time.Sleep(time.Second)
    // 通知任务已完成
    done <- true
}
func main() {
    done := make(chan bool, 1)
    go worker(done)
    // 等待任务完成
    <-done
}

Источник: http://colobu.com/2016/04/14/Golang-Channels/