Одновременная синхронизация и канал в golang

Go

Реализовать параллелизм в 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…