пятница, 27 марта 2020 г.

Таймер и Тикер в Golang

Таймеры и тикеры позволяют выполнять код в будущем, один или несколько раз.

Таймаут (Таймер)

time.After ожидает указанную продолжительность и затем отправляет текущее время по возвращаемому каналу:

select {
case news := <-AFP:
    fmt.Println(news)
case <-time.After(time.Hour):
    fmt.Println("No news in an hour.")
}

Подлежащий time.Timer не будет восстановлен сборщиком мусора, пока не сработает таймер. Если это проблема, используйте взамен time.NewTimer и вызовите его метод Stop, когда таймер больше не нужен:

for alive := true; alive; {
    timer := time.NewTimer(time.Hour)
    select {
    case news := <-AFP:
        timer.Stop()
        fmt.Println(news)
    case <-timer.C:
        alive = false
        fmt.Println("No news in an hour. Service aborting.")
    }
}

Повтор (Тикер)

time.Tick возвращает канал, который доставляет такты (ticks) с одинаковыми интервалами:

go func() {
    for now := range time.Tick(time.Minute) {
        fmt.Println(now, statusUpdate())
    }
}()

Подлежащий time.Ticker не будет восстановлен сборщиком мусора. Если это проблема, используйте взамен time.NewTicker и вызовите его метод Stop, когда тикер больше не нужен.

Ожидать, действовать и отменять

time.AfterFunc ждет в течение указанной продолжительности и затем вызывает функцию в своей собственной goroutine. Возвращает time.Timer, который можно использовать для отмены вызова:

func Foo() {
    timer = time.AfterFunc(time.Minute, func() {
        log.Println("Foo выполняется более минуты.")
    })
    defer timer.Stop()

    // Выполняем какую-либо работу
}


Читайте также:


Купить gopher

четверг, 26 марта 2020 г.

Как остановить goroutine в Golang

Одна goroutine не может принудительно остановить другую.

Чтобы сделать goroutine остановливаемой, позвольте ей слушать сигнал остановки на канале.

quit := make(chan struct{})
go func() {
    for {
        select {
        case <-quit:
            return
        default:
            // …
        }
    }
}()
// …
close(quit)

Иногда удобно использовать один канал для передачи данных и сигнализации.

// Generator возвращает канал, 
// который производит числа 1, 2, 3,…
// Чтобы остановить подлежащую goroutine, закройте канал.
func Generator() chan int {
    ch := make(chan int)
    go func() {
        n := 1
        for {
            select {
            case ch <- n:
                n++
            case <-ch:
                return
            }
        }
    }()
    return ch
}

func main() {
    number := Generator()
    fmt.Println(<-number)
    fmt.Println(<-number)
    close(number)
    // …
}

Вывод:

1
2


Читайте также:


Купить gopher

среда, 25 марта 2020 г.

Передача сигнала по каналу в Golang

Все читатели получают нулевые значения на закрытом канале.

В этом примере функция Publish возвращает канал, который используется для передачи сигнала, когда сообщение было опубликовано.

// Печатает текст по истечении заданного времени.
// По завершении канал wait закрывается.
func Publish(text string, delay time.Duration) (wait <-chan struct{}) {
    ch := make(chan struct{})
    go func() {
        time.Sleep(delay)
        fmt.Println("НОВОСТИ:", text)
        close(ch) // Транслируем для всех получателей.
    }()
    return ch
}

Обратите внимание, что мы используем канал пустых структур: struct{}. Это ясно указывает на то, что канал будет использоваться только для сигнализации, а не для передачи данных.

Вот как вы можете использовать эту функцию.

func main() {
    wait := Publish("Каналы позволяют goroutines общаться.", 5*time.Second)
    fmt.Println("Ожидание новостей...")
    <-wait
    fmt.Println("Время выходить.")
}

Вывод:

Ожидание новостей...
НОВОСТИ: Каналы позволяют goroutines общаться.
Время выходить.


Читайте также:


Купить gopher

вторник, 24 марта 2020 г.

Как отлаживать deadlocks в Golang

Deadlock возникает, когда группа goroutines ждет друг друга, и ни одна из них не может продолжить.

Взгляните на этот простой пример.

func main() {
    ch := make(chan int)
    ch <- 1
    fmt.Println(<-ch)
}

Программа застрянет на операции отправки в канал, ожидая, когда кто-то прочтет значение. Go может обнаруживать подобные ситуации во время выполнения. Вот вывод от нашей программы:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    .../deadlock.go:7 +0x6c

Советы по отладке

goroutine может застрять

  • либо потому, что она ждет канал (channel)
  • либо потому, что она ожидает одну из блокировок (lock) в пакете sync.

Общие причины в том, что

  • ни одна другая goroutine не имеет доступа к каналу или блокировке (lock),
  • группа goroutines ждут друг друга, и никто из них не может продолжить.

В настоящее время Go определяет только, когда программа в целом зависает, а не когда застревает подгруппа goroutines.

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


Читайте также:


Купить gopher

понедельник, 23 марта 2020 г.

Как обнаружить гонки данных в Golang

Гонки данных могут происходить легко и их трудно отлаживать. К счастью, среда выполнения Go часто может помочь.

Используйте -race, чтобы включить встроенный детектор гонки данных.

$ go test -race [packages]
$ go run -race [packages]

Пример

Вот программа с гонкой данных:

package main
import "fmt"

func main() {
    i := 0
    go func() {
        i++ // запись
    }()
    fmt.Println(i) // конкурентное чтение
}

Запуск этой программы с опцией -race говорит нам о гонке между записью в строке 7 и чтением в строке 9:

$ go run -race main.go
0
==================
WARNING: DATA RACE
Write by goroutine 6:
  main.main.func1()
      /tmp/main.go:7 +0x44

Previous read by main goroutine:
  main.main()
      /tmp/main.go:9 +0x7e

Goroutine 6 (running) created at:
  main.main()
      /tmp/main.go:8 +0x70
==================
Found 1 data race(s)
exit status 66

Детали

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

Он работает на darwin/amd64, freebsd/amd64, linux/amd64 и windows/amd64.

Накладные расходы варьируются, но обычно увеличивается объем используемой памяти в 5-10 раз, а время выполнения увеличивается в 2-20 раз.


Читайте также:


Купить gopher

четверг, 19 марта 2020 г.

Гонки данных в Golang простыми словами

Гонка данных (data race) происходит, когда две go-процедуры (goroutines) одновременно обращаются к одной и той же переменной, и, по крайней мере, одно из обращений является записью.

Гонки данных довольно распространены и могут быть очень сложными для отладки.

Эта функция имеет гонку данных, и ее поведение не определено. Она может, например, напечатать число 1 (хотя задумывалось 2).

func race() {
    wait := make(chan struct{})
    n := 0
    go func() {
        n++ // чтение, приращение, запись
        close(wait)
    }()
    n++ // конфликт доступа
    <-wait
    fmt.Println(n) // Вывод: неопределен
}

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

g1 g2
Читает значение 0 из n.
Читает значение 0 из n.
Увеличивает значение с 0 до 1.
Записывает 1 в n.
Увеличивает значение с 0 до 1.
Записывает 1 в n.
Печатает n, которое сейчас равно 1.

Название "гонка данных" (data race) вводит в заблуждение. Не только порядок операций не определен - очень мало гарантий. И компиляторы, и аппаратные средства часто переворачивают код с ног на голову и выворачивают его наизнанку для достижения лучшей производительности. Если вы посмотрите на поток в середине действия, вы можете увидеть почти все что угодно.

Как избежать гонок данных

Единственный способ избежать гонки данных - это синхронизировать доступ ко всем изменяемым данным, которые совместно используются потоками. Есть несколько способов добиться этого. В Go вы обычно используете канал или блокировку (lock). (Низкоуровневые механизмы доступны в пакетах sync и sync/atomic.)

Предпочтительным способом обработки одновременного доступа к данным в Go является использование канала для передачи фактических данных из одной go-процедуры в другую. Девиз: "Не общайтесь, делясь памятью; делитесь памятью, общаясь".

func sharingIsCaring() {
    ch := make(chan int)
    go func() {
        n := 0 // Локальная переменная видна только одной 
               // goroutine.
        n++
        ch <- n // Данные покидают одну goroutine...
    }()
    n := <-ch // ...и благополучно прибывает в другую.
    n++
    fmt.Println(n) // Вывод: 2
}

В этом коде канал выполняет двойную функцию:

  • он передает данные из одной goroutine в другую,
  • и он действует как точка синхронизации.

Отправляющая goroutine будет ждать, пока другая goroutine получит данные, а принимающая goroutine будет ждать, пока другая goroutine отправит данные.

Модель памяти Go - условия, при которых считывания переменной в одной goroutine могут гарантированно наблюдать значения, полученные в результате записи в одну и ту же переменную в другой goroutine, - довольно сложна, но при условии, что вы совместно используете все изменяемые данные между goroutines через каналы, вы защищены от гонок данных.


Читайте также:


Купить gopher

вторник, 17 марта 2020 г.

Select ожидает по группе каналов

Оператор select ожидает нескольких операций отправки или получения одновременно.

Оператор блокируется целиком, пока одна из операций не станет разблокированной.

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

// блокируется, пока не появятся данные, 
// доступные на ch1 или ch2
select {
case <-ch1:
    fmt.Println("Получено из ch1")
case <-ch2:
    fmt.Println("Получено из ch2")
}

Операции отправки и получения по nil каналу блокируется навсегда. Это можно использовать для отключения канала в операторе select:

ch1 = nil // отключает этот канал
select {
case <-ch1:
    fmt.Println("Получено из ch1") // не произойдет
case <-ch2:
    fmt.Println("Получено из ch2")
}

default случай

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

// никогда не блокирует
select {
case x := <-ch:
    fmt.Println("Получено", x)
default:
    fmt.Println("Нет ничего доступного")
}

Примеры


Бесконечная случайная двоичная последовательность

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

rand := make(chan int)
for {
    select {
    case rand <- 0: // нет оператора
    case rand <- 1:
    }
}

Операция блокировки с тайм-аутом

Функция time.After является частью стандартной библиотеки; она ожидает истечения заданного времени и затем отправляет текущее время по возвращаемому каналу.

select {
case news := <-AFP:
    fmt.Println(news)
case <-time.After(time.Minute):
    fmt.Println("Время вышло: Нет новостей в течение минуты")
}

Утверждение, которое блокирует навсегда

Оператор select блокируется до тех пор, пока не будет продолжен хотя бы один из его случаев. При нулевых случаях это никогда не произойдет.

select {}

Типичное использование будет в конце основной функции в некоторых многопоточных программах. Когда main возвращается, программа завершается и не ожидает завершения других go-процедур.


Читайте также:


Купить gopher