Multithreading in Go
- Golang Team Lead в Lohika
- Опыт в Golang 4+ года
- Разработчик ноунейм либ на гитхайбе
- Чел, который будет втирать вам дичь
следующие 45 минут
Process vs thread vs goroutine
Что такое процесс (process)?
● Процесс - это состояние программы, исполняемой на компьютере с
помощью операционной системы.
● В процесс входит:
○ Память (адресное пространство). Код и данные исполняющейся программы находятся в
памяти процесса.
○ Потоки (threads). В процессе всегда есть минимум один поток, в котором исполняется
код программы
○ Средства связи с внешним миром (сетевые подключения, файловые системы,
переменные окружения и т.д.).
Что такое поток (thread)?
● Поток - это абстракция операционной системы, последовательно
исполняющая код программы внутри процесса и хранящая локальные
данные для этого кода.
● Программа может запустить несколько потоков, которые будут
исполнять разные участки кода программы внутри одного процесса.
● Операционная система распределяет потоки по свободным ядрам
процессора, чтобы они могли исполняться параллельно.
● На одноядерном компьютере операционная система чередует
исполнение потоков, эмулируя их параллельное исполнение.
Что такое поток (thread)?
● У потоков из разных процессов - разная память.
● У потоков из одного процесса - общая память.
● Потоки одного процесса могут как читать из общей памяти, так и
записывать в нее.
Concurrency and parallelism
Source: https://1.bp.blogspot.com/-TKQSNUbkqNs/W_UCljTAeSI/AAAAAAAAI3Y/FK_seKThNSghF1eNh0wkb7fwTcPyxanyQCLcBGAs/s1600/1.jpg
Source: https://avatars.mds.yandex.net/get-zen_doc/1908497/pub_5d6fc8a4fbe6e700ade48b11_5dea38857cccba00adf2cf91/orig
Что такое горутина (goroutine)?
● Горутина - это абстракция Go, внешне ничем не отличимая от потока.
● Для программиста на Go горутина - это и есть поток.
● Горутины легковеснее потока
Под капотом
● Горутины исполняются на обычных потоках операционной системы.
● В каждый момент времени на каждом потоке может исполняться только
одна горутина.
● Переключением горутин между потоками занимается Go.
● Количество одновременных потоков, исполняющих горутины,
ограничено с помощью GOMAXPROCS.
● Такая схема позволяет “загрузить” все ядра процессора полезной
работой, минимизируя накладные расходы операционной системы на
переключение между потоками.
Проблемы?
Data race
Race condition
Deadlock
Каналы (channels)
Do not communicate by sharing memory;
instead, share memory by communicating.
Каналы (channels)
● Канал - это блокирующая очередь (blocking queue):
○ Запись в полный канал блокируется до тех пор, пока кто-то из него не прочитает.
○ Чтение из пустого канала блокируется до тех пор, пока кто-то в него не запишет.
● Позволяет передавать произвольные данные между горутинами без data
races.
● Много горутин могут одновременно читать из одного канала.
● Много горутин могут одновременно записывать в один канал.
Source: https://habr.com/ru/post/308070/
количество элементов в буфере
размерность буфера
указатель на буфер для элементов канала
флаг, указывающий, закрыт канал или нет
список горутин, ожидающих чтения из
канала
список горутин, ожидающих запись в канал
мьютекс для безопасного доступа к каналу
ch1 := make(chan string)
Создание канала
ch1 <- "test"
Запись в канал
var1 := <- ch1
Чтение из канала
close(ch1)
Закрытие канала
var1, ok := <- ch1
Чтение из канала (+close)
Другие инструменты для многопоточности
● Пакет sync
○ sync.Mutex
○ sync.WaitGroup
○ sync.Once
● Пакет sync/atomic
○ atomic.Value
○ Атомарные операции
sync.Mutex
● sync.Mutex - “обычный” мьютекс из других языков программирования.
● Используется для защиты области памяти от одновременного доступа
из нескольких горутин, т.е. от data races и race conditions
● Go гарантирует, что в каждый момент времени только одна горутина
может удерживать мьютекс. Все остальные будут ожидать своей
очереди на Mutex.Lock.
● Есть RWMutex. Удобен когда нужно много читать и мало записывать
sync.WaitGroup
● sync.WaitGroup предназначен для ожидания завершения множества
горутин.
● С помощью sync.WaitGroup легко запустить много горутин, выполняющих
полезную работу, после чего дождаться их завершения.
Атомарные операции
● sync/atomic содержит много функций для низкоуровневых атомарных
операций, которые помогают избегать data races.
● С помощью этих функций обычно реализуются атомарные счетчики без
мьютексов.
Deadlock
Deadlock
● Deadlock - взаимная блокировка горутин, при которой дальнейшая
работа потоков невозможна.
● Обычно возникает когда несколько разных мьютексов блокируются из
разных горутин в разном порядке.
● Может возникать не только на мьютексах, но и на каналах.
Пример deadlock’а на мьютексах
package main
import "sync"
func main() {
var mu1, mu2 sync.Mutex
ch := make(chan string)
go func() {
for {
mu1.Lock(); mu2.Lock()
ch <- "foo"
mu1.Unlock(); mu2.Unlock()
}
}()
go func() {
for {
mu2.Lock(); mu1.Lock() // <- different lock order
ch <- "bar"
mu2.Unlock(); mu1.Unlock()
}
}()
for s := range ch { println(s) }
}
Пример deadlock’а на channel’ах
package main
import "sync"
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
var wg sync.WaitGroup
wg.Add(1)
go func() {
s := <-ch1
ch2 <- s
wg.Done()
}()
wg.Add(1)
go func() {
s := <-ch2
ch1 <- s
wg.Done()
}()
wg.Wait() // <- never unblocks due to deadlock
}
Пример data race
package main
import (
"sync"
)
func main() {
var counter int64
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
counter++
wg.Done()
}()
}
wg.Wait()
}
Пример race condition
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
counter := int64(0)
var wg sync.WaitGroup
wg.Add(1000)
for i := 0; i < 1000; i++ {
go func() {
atomic.AddInt64(&counter, 1)
c1 := atomic.LoadInt64(&counter)
if c1 == 42 {
fmt.Println("Lucky number!")
c2 := atomic.LoadInt64(&counter)
fmt.Printf("Counter1 is %d, Counter2 %d", c1, c2)
}
wg.Done()
}()
}
wg.Wait()
}
Как не допустить data races
● Чтение общей памяти из нескольких горутин не приводит к data races.
● Изменение общей памяти, когда кто-то из нее может читать, приводит к
data races.
● Проектируйте программу так, чтобы горутины общались через каналы, а
не через общую память.
● Если общение через каналы не получается либо усложняет программу,
то защищайте изменяемую общую память мьютексами.
Признаки data races
● Программа периодически “падает” в неожиданных местах.
● В программе самопроизвольно появляются “испорченные” данные.
● Программа периодически “зависает”.
● Чем выше “нагрузка” на программу, тем выше вероятность появления
вышеуказанных “глюков”.
● Data races могут очень долго себя не проявлять.
Как обнаружить скрытые data races
● Go предоставляет data race detector, который может выявить
большинство data races в ваших программах.
● Просто передайте параметр -race в go build или go test:
○ go build -race
○ go test -race
● Программа, собранная с флагом -race, работает намного медленнее
программы, собранной без этого флага. Поэтому запускайте ее только в
среде с контролируемой нагрузкой.
● Пишите многопоточные тесты и регулярно прогоняйте их с флагом -race.
Context
● Вспомогательное средство для оркестрации и правильной работы
горутин
● Зачастую нужно передавать во все ф-ции в качестве первого параметра
● WithDeadline, WithCancel, WithTimeout не завершает горутины. Всю
логику нужно описывать самостоятельно
● WithValue можно использовать для передачи не критически важных
сопутствующих параментов, например trace ID
Итоги
● Каналы + горутины = супер-инструмент для многопоточного
программирования.
● В Go есть пакеты sync и sync/atomic с полезными инструментами для
многопоточного программирования.
● Go не избавляет от data race’ов, race condition’ов и deadlock
● Go предоставляет удобные средства для написания data race-free кода,
плюс data race detector, выявляющий большинство data race’ов.
- https://tour.golang.org/
- https://golang.org/doc/effective_go
- https://blog.golang.org/
- https://github.com/golang/go/wiki/CodeReviewComments
- https://golang.org/doc/
- http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
- https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html
- https://dave.cheney.net/
Go runtime
Cooperating Scheduler Preemptive scheduler

Multithreading in go

  • 1.
  • 3.
    - Golang TeamLead в Lohika - Опыт в Golang 4+ года - Разработчик ноунейм либ на гитхайбе - Чел, который будет втирать вам дичь следующие 45 минут
  • 4.
    Process vs threadvs goroutine
  • 5.
    Что такое процесс(process)? ● Процесс - это состояние программы, исполняемой на компьютере с помощью операционной системы. ● В процесс входит: ○ Память (адресное пространство). Код и данные исполняющейся программы находятся в памяти процесса. ○ Потоки (threads). В процессе всегда есть минимум один поток, в котором исполняется код программы ○ Средства связи с внешним миром (сетевые подключения, файловые системы, переменные окружения и т.д.).
  • 6.
    Что такое поток(thread)? ● Поток - это абстракция операционной системы, последовательно исполняющая код программы внутри процесса и хранящая локальные данные для этого кода. ● Программа может запустить несколько потоков, которые будут исполнять разные участки кода программы внутри одного процесса. ● Операционная система распределяет потоки по свободным ядрам процессора, чтобы они могли исполняться параллельно. ● На одноядерном компьютере операционная система чередует исполнение потоков, эмулируя их параллельное исполнение.
  • 7.
    Что такое поток(thread)? ● У потоков из разных процессов - разная память. ● У потоков из одного процесса - общая память. ● Потоки одного процесса могут как читать из общей памяти, так и записывать в нее.
  • 8.
    Concurrency and parallelism Source:https://1.bp.blogspot.com/-TKQSNUbkqNs/W_UCljTAeSI/AAAAAAAAI3Y/FK_seKThNSghF1eNh0wkb7fwTcPyxanyQCLcBGAs/s1600/1.jpg
  • 9.
  • 10.
    Что такое горутина(goroutine)? ● Горутина - это абстракция Go, внешне ничем не отличимая от потока. ● Для программиста на Go горутина - это и есть поток. ● Горутины легковеснее потока
  • 11.
    Под капотом ● Горутиныисполняются на обычных потоках операционной системы. ● В каждый момент времени на каждом потоке может исполняться только одна горутина. ● Переключением горутин между потоками занимается Go. ● Количество одновременных потоков, исполняющих горутины, ограничено с помощью GOMAXPROCS. ● Такая схема позволяет “загрузить” все ядра процессора полезной работой, минимизируя накладные расходы операционной системы на переключение между потоками.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
    Do not communicateby sharing memory; instead, share memory by communicating.
  • 18.
    Каналы (channels) ● Канал- это блокирующая очередь (blocking queue): ○ Запись в полный канал блокируется до тех пор, пока кто-то из него не прочитает. ○ Чтение из пустого канала блокируется до тех пор, пока кто-то в него не запишет. ● Позволяет передавать произвольные данные между горутинами без data races. ● Много горутин могут одновременно читать из одного канала. ● Много горутин могут одновременно записывать в один канал.
  • 19.
    Source: https://habr.com/ru/post/308070/ количество элементовв буфере размерность буфера указатель на буфер для элементов канала флаг, указывающий, закрыт канал или нет список горутин, ожидающих чтения из канала список горутин, ожидающих запись в канал мьютекс для безопасного доступа к каналу
  • 20.
    ch1 := make(chanstring) Создание канала ch1 <- "test" Запись в канал var1 := <- ch1 Чтение из канала close(ch1) Закрытие канала var1, ok := <- ch1 Чтение из канала (+close)
  • 21.
    Другие инструменты длямногопоточности ● Пакет sync ○ sync.Mutex ○ sync.WaitGroup ○ sync.Once ● Пакет sync/atomic ○ atomic.Value ○ Атомарные операции
  • 22.
    sync.Mutex ● sync.Mutex -“обычный” мьютекс из других языков программирования. ● Используется для защиты области памяти от одновременного доступа из нескольких горутин, т.е. от data races и race conditions ● Go гарантирует, что в каждый момент времени только одна горутина может удерживать мьютекс. Все остальные будут ожидать своей очереди на Mutex.Lock. ● Есть RWMutex. Удобен когда нужно много читать и мало записывать
  • 23.
    sync.WaitGroup ● sync.WaitGroup предназначендля ожидания завершения множества горутин. ● С помощью sync.WaitGroup легко запустить много горутин, выполняющих полезную работу, после чего дождаться их завершения.
  • 24.
    Атомарные операции ● sync/atomicсодержит много функций для низкоуровневых атомарных операций, которые помогают избегать data races. ● С помощью этих функций обычно реализуются атомарные счетчики без мьютексов.
  • 25.
  • 26.
    Deadlock ● Deadlock -взаимная блокировка горутин, при которой дальнейшая работа потоков невозможна. ● Обычно возникает когда несколько разных мьютексов блокируются из разных горутин в разном порядке. ● Может возникать не только на мьютексах, но и на каналах.
  • 27.
    Пример deadlock’а намьютексах package main import "sync" func main() { var mu1, mu2 sync.Mutex ch := make(chan string) go func() { for { mu1.Lock(); mu2.Lock() ch <- "foo" mu1.Unlock(); mu2.Unlock() } }() go func() { for { mu2.Lock(); mu1.Lock() // <- different lock order ch <- "bar" mu2.Unlock(); mu1.Unlock() } }() for s := range ch { println(s) } }
  • 28.
    Пример deadlock’а наchannel’ах package main import "sync" func main() { ch1 := make(chan string) ch2 := make(chan string) var wg sync.WaitGroup wg.Add(1) go func() { s := <-ch1 ch2 <- s wg.Done() }() wg.Add(1) go func() { s := <-ch2 ch1 <- s wg.Done() }() wg.Wait() // <- never unblocks due to deadlock }
  • 29.
    Пример data race packagemain import ( "sync" ) func main() { var counter int64 var wg sync.WaitGroup wg.Add(100) for i := 0; i < 100; i++ { go func() { counter++ wg.Done() }() } wg.Wait() }
  • 30.
    Пример race condition packagemain import ( "fmt" "sync" "sync/atomic" ) func main() { counter := int64(0) var wg sync.WaitGroup wg.Add(1000) for i := 0; i < 1000; i++ { go func() { atomic.AddInt64(&counter, 1) c1 := atomic.LoadInt64(&counter) if c1 == 42 { fmt.Println("Lucky number!") c2 := atomic.LoadInt64(&counter) fmt.Printf("Counter1 is %d, Counter2 %d", c1, c2) } wg.Done() }() } wg.Wait() }
  • 31.
    Как не допуститьdata races ● Чтение общей памяти из нескольких горутин не приводит к data races. ● Изменение общей памяти, когда кто-то из нее может читать, приводит к data races. ● Проектируйте программу так, чтобы горутины общались через каналы, а не через общую память. ● Если общение через каналы не получается либо усложняет программу, то защищайте изменяемую общую память мьютексами.
  • 32.
    Признаки data races ●Программа периодически “падает” в неожиданных местах. ● В программе самопроизвольно появляются “испорченные” данные. ● Программа периодически “зависает”. ● Чем выше “нагрузка” на программу, тем выше вероятность появления вышеуказанных “глюков”. ● Data races могут очень долго себя не проявлять.
  • 33.
    Как обнаружить скрытыеdata races ● Go предоставляет data race detector, который может выявить большинство data races в ваших программах. ● Просто передайте параметр -race в go build или go test: ○ go build -race ○ go test -race ● Программа, собранная с флагом -race, работает намного медленнее программы, собранной без этого флага. Поэтому запускайте ее только в среде с контролируемой нагрузкой. ● Пишите многопоточные тесты и регулярно прогоняйте их с флагом -race.
  • 34.
    Context ● Вспомогательное средстводля оркестрации и правильной работы горутин ● Зачастую нужно передавать во все ф-ции в качестве первого параметра ● WithDeadline, WithCancel, WithTimeout не завершает горутины. Всю логику нужно описывать самостоятельно ● WithValue можно использовать для передачи не критически важных сопутствующих параментов, например trace ID
  • 35.
    Итоги ● Каналы +горутины = супер-инструмент для многопоточного программирования. ● В Go есть пакеты sync и sync/atomic с полезными инструментами для многопоточного программирования. ● Go не избавляет от data race’ов, race condition’ов и deadlock ● Go предоставляет удобные средства для написания data race-free кода, плюс data race detector, выявляющий большинство data race’ов.
  • 36.
    - https://tour.golang.org/ - https://golang.org/doc/effective_go -https://blog.golang.org/ - https://github.com/golang/go/wiki/CodeReviewComments - https://golang.org/doc/ - http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/ - https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html - https://dave.cheney.net/
  • 38.