Реализовать параллелизм в golang очень просто, просто добавьте ключевое слово «go» перед функцией, которой требуется параллелизм, но как быть с синхронизацией и связью между разными горутинами в механизме параллелизма go, golang предоставляет пакет синхронизации и механизм канала для решения этой проблемы.вопрос.
Пакет sync предоставляет базовые примитивы синхронизации, такие как мьютексы.Типы, отличные от Once и WaitGroup, в основном используются в подпрограммах низкоуровневых библиотек. Более сложные операции синхронизации осуществляются через каналы и коммуникации.
- type Cond
- func NewCond(l Locker) *Cond
- func (c *Cond) Broadcast()
- func (c *Cond) Signal()
- func (c *Cond) Wait()
- type Locker
- type Mutex
- func (m *Mutex) Lock()
- func (m *Mutex) Unlock()
- type Once
- func (o *Once) Do(f func())
- type Pool
- func (p *Pool) Get() interface{}
- func (p *Pool) Put(x interface{})
- type RWMutex
- func (rw *RWMutex) Lock()
- func (rw *RWMutex) RLock()
- func (rw *RWMutex) RLocker() Locker
- func (rw *RWMutex) RUnlock()
- func (rw *RWMutex) Unlock()
- type WaitGroup
- func (wg *WaitGroup) Add(delta int)
- func (wg *WaitGroup) Done()
- func (wg *WaitGroup) Wait()
Синхронизация в golang осуществляется через sync.WaitGroup. Функция группы ожидания: он реализует структуру, подобную очереди, которая всегда может добавлять задачи в очередь и удалять их из очереди, когда задача завершена.Если задача в очереди не полностью завершена, она может быть заблокирована Функция Wait().Предотвращает продолжение работы программы до тех пор, пока не будут завершены все задачи в очереди.
Группа WaitGroup имеет всего три метода: Add(delta int), Done(), Wait().
Добавить: добавить или уменьшить количество ожидающих горутин.
Готово: эквивалентно добавлению (-1)
Подождите: блокируйте, пока все счетчики WaitGroup не станут 0
Конкретные примеры следующие:
package main
import (
"fmt"
"sync"
)
var waitgroup sync.WaitGroup
func Afunction(shownum int) {
fmt.Println(shownum)
waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}
func main() {
for i := 0; i < 10; i++ {
waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
go Afunction(i)
}
waitgroup.Wait() //.Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
}
используемые сцены:
В программе требуется параллелизм, необходимо создать несколько горутин, и следующее выполнение программы должно быть завершено после завершения всех этих параллелизма. Характерной чертой группы ожидания является то, что функцию Wait() можно использовать для блокировки до тех пор, пока все задачи в очереди не будут завершены, и разблокировать ее, вместо того, чтобы засыпать в течение фиксированного времени ожидания. Но его недостаток в том, что он не может указать фиксированное количество горутин.
Канальный механизм:
По сравнению с sync.WaitGroup гораздо проще использовать канал для практики синхронизации в golang. Сам канал может добиться блокировки, которая передает данные через
Создать канал (реализуется функцией make(), включая небуферизованный канал и буферизованный канал);
Добавить данные в канал (channel
прочитать данные из канала (data
Закрыть канал (реализовано функцией close(), после закрытия вы больше не можете хранить данные в канале, но можете продолжать читать данные из канала)
Каналы делятся на буферизованные и небуферизованные каналы.Методы создания двух каналов следующие:
var ch = make(chan int) // небуферизованный канал, эквивалентный make(chan int ,0)
var ch = make(chan int,10) //Есть буферный канал, размер буфера 5
Среди них небуферизованный канал будет блокироваться как при чтении, так и при записи, а буферизованный канал может сохранять данные в канале, когда данные, хранящиеся в канале, не достигают общего числа буферов канала, пока буфер не заполнится. Из-за наличия блокировки особое внимание следует уделить способу использования при использовании канала, чтобы предотвратить возникновение взаимоблокировки. Примеры следующие:
Небуферизованный канал:
package main
import "fmt"
func Afuntion(ch chan int) {
fmt.Println("finish")
<-ch
}
func main() {
ch := make(chan int) //无缓冲的channel
go Afuntion(ch)
ch <- 1
// 输出结果:
// finish
}
Анализ кода: сначала создайте небуферизованный канал ch, а затем выполните go Afuntion(ch), затем выполните
package main
import "fmt"
func Afuntion(ch chan int) {
fmt.Println("finish")
<-ch
}
func main() {
ch := make(chan int) //无缓冲的channel
//只是把这两行的代码顺序对调一下
ch <- 1
go Afuntion(ch)
// 输出结果:
// 死锁,无结果
}
Анализ кода: Сначала создайте небуферизованный канал, а затем запишите данные в канал ch в основной сопрограмме через команду ch Суммировать:
Для некешированного канала две операции ввода канала и извлечения данных из канала не могут быть помещены в одну и ту же сопрограмму, чтобы предотвратить возникновение взаимоблокировки; в то же время вы должны сначала использовать go, чтобы открыть сопрограмму для работы с ней. канал, В этот момент сопрограмма go блокируется, а затем выполняется обратная операция канала в основной сопрограмме (аналогично go Сопрограмма выполняет обратную операцию над каналом) и реализует разблокировку сопрограммы go. То есть сопрограмма go должна быть впереди, а сопрограмма unlock должна быть позади.
С буферизованным каналом:
Для канала с буфером, пока буфер в канале не заполнен, вы можете сохранять данные в канале до тех пор, пока буфер не заполнится; аналогично, пока буфер в канале не равен 0, вы всегда можете извлекать данные из канала до тех пор, пока буферы канала не станут равными 0 для блокировки.
Из этого видно, что по сравнению с каналом без кеша, канал с кешем с меньшей вероятностью вызовет взаимоблокировку, и при этом может уверенно использоваться в горутине.
close():
close в основном используется для закрытия канала.Его использование close(channel), и канал закрывается в месте производителя, а не в месте потребителя. А после закрытия канала вы больше не можете хранить данные в канале, но можете продолжать читать данные из канала. Примеры следующие:
package main
import "fmt"
func main() {
var ch = make(chan int, 20)
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
//ch <- 11 //panic: runtime error: send on closed channel
for i := range ch {
fmt.Println(i) //输出0 1 2 3 4 5 6 7 8 9
}
}
Обработка тайм-аута блокировки канала:
Горутина иногда попадает в ситуацию блокировки, так как же избежать блокировки всей программы из-за блокировки канала? Решение: Установите время ожидания обработки с помощью выбора, конкретная процедура выглядит следующим образом:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case i := <-c:
fmt.Println(i)
case <-time.After(time.Duration(3) * time.Second): //设置超时时间为3s,如果channel 3s钟没有响应,一直阻塞,则报告超时,进行超时处理.
fmt.Println("timeout")
o <- true
break
}
}
}()
<-o
}
резюме параллелизма golang:
Два способа параллелизма: sync.WaitGroup, самым большим преимуществом этого метода является то, что функция Wait() может блокироваться до тех пор, пока все задачи в очереди не будут выполнены перед разблокировкой, но его недостатком является то, что он не может указать количество параллельных сопрограмм.
Преимущества канала: более гибко использовать канал с кешем для указания параллельных горутин. Но его недостатком является то, что при неправильном использовании легко вызвать взаимоблокировку, а также необходимо определить, завершилось ли выполнение параллельной горутины.
Однако, условно говоря, каналы являются более гибкими и удобными в использовании.В то же время тупика программы, вызванного каналами, можно избежать с помощью механизма обработки тайм-аута.Поэтому использование каналов для реализации параллелизма программ более удобно и проще. использовать.
использованная литература:
изучите golang.com/articles/31…
изучите golang.com/articles/26…