내 연락처 정보
우편메소피아@프로톤메일.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
코루틴은 독립적인 스택 공간을 갖고 프로그램의 힙 공간을 공유하는 사용자 수준의 경량 스레드입니다.
단일 스레드 기반의 알고리즘을 통해 구현된 마이크로 스레드입니다. 멀티 스레드 프로그래밍과 비교하면 다음과 같은 장점이 있습니다.
채널은 코루틴 간의 통신에 사용되는 데이터 구조입니다. 대기열과 유사하게 한쪽 끝은 발신자이고 다른 쪽 끝은 수신자입니다. 채널을 사용하면 데이터의 동기화 및 순서를 보장할 수 있습니다.
채널은 버퍼링된 채널과 버퍼링되지 않은 채널로 구분되며 다음과 같이 선언됩니다.
intChan := make(chan int,<缓冲容量>)
intChan := make(chan int)
버퍼링된 채널과 버퍼링되지 않은 채널의 차이점:
버퍼링되지 않은 채널을 구현할 때 주의해야 할 점은 채널 양쪽 끝에 송신자와 수신자가 있어야 하며, 그렇지 않으면 교착 상태가 발생한다는 것입니다.
질문 의미: 코루틴 채널을 사용하여 숫자 1-10과 문자 AJ를 번갈아 인쇄합니다.
암호:
- package main
-
- import (
- "fmt"
- "sync"
- )
-
- /*
- 无缓冲chanel:需要在写入chanel的时候要保证有另外一个协程在读取chanel。否则会导致写端阻塞,发生死锁
- 解决办法:
- 避免死锁的发生:
- 当i循环到10时,printAlp协程已然结束,所以此时不必再写入alp通道
- */
-
- func printNum(wg *sync.WaitGroup, numCh chan struct{}, alpCh chan struct{}) {
- defer wg.Done()
-
- for i := 1; i <= 10; i++ {
- <-alpCh // 等待字母goroutine发信号
- fmt.Print(i, " ")
- //避免死锁发生
- if i < 10 {
- numCh <- struct{}{} // 发信号给字母goroutine
- }
- if i == 10 {
- close(numCh)
- }
- }
-
- }
-
- func printAlp(wg *sync.WaitGroup, numCh chan struct{}, alpCh chan struct{}) {
- defer wg.Done()
-
- for i := 'A'; i <= 'J'; i++ {
- <-numCh // 等待数字goroutine发信号
- fmt.Printf("%c", i)
- alpCh <- struct{}{} // 发信号给数字goroutine
- }
- close(alpCh)
- }
-
- func main() {
- numCh := make(chan struct{}) // 用于数字goroutine的信号通道
- alpCh := make(chan struct{}) // 用于字母goroutine的信号通道
- var wg sync.WaitGroup
-
- wg.Add(2)
-
- go printAlp(&wg, numCh, alpCh)
- go printNum(&wg, numCh, alpCh)
-
- // 启动时先给数字goroutine发送一个信号
- numCh <- struct{}{}
-
- wg.Wait()
-
- }
주제 분석:
이 질문에서는 문자와 숫자를 교대로 인쇄해야 하므로 버퍼링되지 않은 채널의 애플리케이션 시나리오와 일치하는 두 코루틴의 엄격한 순서를 보장해야 합니다. 숫자와 문자를 각각 저장하는 두 개의 채널을 설정합니다. 숫자와 문자를 인쇄하는 두 개의 코루틴은 각각 두 채널의 발신자와 수신자 역할을 합니다. 루프에서 한 번 인쇄하고 신호를 한 번 보내 다른 코루틴에 인쇄하도록 상기시킵니다.
마지막 문자 '10'이 인쇄되면 문자 인쇄를 위한 코루틴이 종료되고, 이때 numCh 채널에는 수신자가 없습니다. 이때 unbuffered 채널의 구현 조건은 더 이상 충족되지 않습니다. 그렇지 않으면 신호를 다시 보내면 차단 교착 상태가 발생합니다. 따라서 10번째로 다시 신호를 보낼 필요가 없습니다.
제목: 다중 코루틴 + 채널 프로그래밍 모델을 사용하여 다중 작업 동시 처리의 비즈니스 시나리오를 구현하고 작업이 추가되는 순서대로 예약 순서를 지정하는 작업 스케줄러를 설계합니다.
암호:
- type scheduler struct {
- taskChan chan func()
- wt sync.WaitGroup
- }
-
- func (td *scheduler) AddTask(task func()) {
- td.taskChan <- task
- }
-
- func (td *scheduler) Executer() {
- defer td.wt.Done()
- for {
- task, ok := <-td.taskChan
- task()
- if ok && len(td.taskChan) == 0 {
- break
- }
- }
- }
-
- func (td *scheduler) Start() {
- td.wt.Add(4)
- //假设四个消费者
- for i := 0; i < 4; i++ {
- go td.Executer()
- }
-
- td.wt.Wait()
- }
-
- func main() {
- sd := scheduler{
- taskChan: make(chan func(), 5),
- }
-
- go func() {
- sd.AddTask(func() {
- fmt.Println("任务1")
- })
- sd.AddTask(func() {
- fmt.Println("任务2")
- })
- sd.AddTask(func() {
- fmt.Println("任务3")
- })
- sd.AddTask(func() {
- fmt.Println("任务4")
- })
- sd.AddTask(func() {
- fmt.Println("任务5")
- })
- sd.AddTask(func() {
- fmt.Println("任务6")
- })
- close(sd.taskChan)
- }()
-
- sd.Start()
-
- }
문제 분석:
추가된 작업은 다중 작업이므로 둘 이상이고 이러한 작업을 실행하려면 비동기 처리가 필요합니다. 버퍼링된 채널을 준수하려면 향상된 처리량과 비동기 처리가 필요합니다.
그런 다음 작업을 채널에 넣어야 하며, 여러 수신자가 채널에서 작업을 순서대로 가져와 실행할 수 있습니다.
주의가 필요한 문제는 추가된 작업 개수가 채널의 버퍼보다 많으면 작업 추가 시 차단이 발생한다는 점이다. 컨슈머의 정상적인 시작에 영향을 주지 않기 위해서는 별도의 코루틴을 열어 작업을 추가해야 합니다.
이런 식으로 소비자가 소비하면 차단 생산자가 깨어나 작업을 계속 추가합니다.
코루틴 + 채널 프로그래밍 모델을 연구한 후에는 제목에서 방금 언급한 것 외에도 다음 문제에도 주의를 기울여야 합니다.
우선, 채널 폐쇄의 가장 기본적인 원칙은 이미 폐쇄된 채널을 폐쇄하지 않는 것입니다. 둘째, Go 채널을 사용하는 또 다른 원칙이 있습니다.데이터 수신자 또는 송신자가 여러 명인 경우 채널을 닫지 마십시오.다시 말해서,채널의 유일한 발신자만 이 채널을 닫도록 해야 합니다.
무례한 방법은 예외 복구를 통해 채널을 닫는 것이지만 이는 분명히 위의 원칙을 위반하고 데이터 경쟁을 일으킬 수 있습니다. 또 다른 방법은 동시 닫기가 보장되지 않는 sync.Once 또는 sync.Mutex를 사용하여 채널을 닫는 것입니다. 채널에서의 작업 및 전송 작업은 데이터 경합을 생성하지 않습니다. 두 가지 방법 모두 문제가 있기 때문에 여기서는 채널을 정상적으로 닫는 방법에 대해 자세히 소개하지 않겠습니다.
대처하기 가장 쉬운 상황 중 하나입니다. 발신자가 전송을 완료해야 할 경우 채널을 닫도록 하세요. 이는 위의 두 프로그래밍 예제의 경우입니다.
Go 채널의 기본 원칙에 따르면 채널의 유일한 발신자에서만 채널을 닫을 수 있습니다. 따라서 이 경우 어딘가에서 직접 채널을 닫을 수는 없습니다.그러나 송신자에게 더 이상 데이터를 보내지 말라고 알리기 위해 수신자가 추가 신호 채널을 닫도록 할 수 있습니다.。
- package main
-
- import (
- "log"
- "sync"
- )
-
- func main() {
-
- cosnt N := 5
- cosnt Max := 60000
- count := 0
-
- dataCh := make(chan int)
- stopCh := make(chan bool)
-
- var wt sync.WaitGroup
- wt.Add(1)
-
- //发送者
- for i := 0; i < N; i++ {
- go func() {
- for {
- select {
- case <-stopCh:
- return
- default:
- count += 1
- dataCh <- count
- }
- }
- }()
- }
-
- //接收者
- go func() {
- defer wt.Done()
- for value := range dataCh {
- if value == Max {
- // 此唯一的接收者同时也是stopCh通道的
- // 唯一发送者。尽管它不能安全地关闭dataCh数
- // 据通道,但它可以安全地关闭stopCh通道。
- close(stopCh)
- return
- }
- log.Println(value)
- }
- }()
-
- wt.Wait()
- }
이 방법에서는 수신자가 더 이상 데이터를 수신할 필요가 없음을 발신자에게 알리기 위해 사용하는 추가 신호 채널 stopCh를 추가합니다. 또한 이 메서드는 dataCh를 닫지 않습니다. 코루틴에서 더 이상 채널을 사용하지 않으면 채널이 닫혔는지 여부에 관계없이 점차적으로 가비지 수집됩니다.
이 방법의 장점은 한 채널을 닫으면 다른 채널 사용을 중지하여 간접적으로 다른 채널을 닫는다는 것입니다.
수신자나 발신자가 데이터 전송에 사용되는 채널을 닫도록 할 수 없으며, 여러 수신자 중 하나가 추가 신호 채널을 닫도록 할 수도 없습니다. 이 두 가지 관행 모두 채널 폐쇄 원칙을 위반합니다.
그러나 우리가 소개할 수 있는 것은중간 중재자 역할 및 모든 수신자와 발신자에게 작업 종료를 알리기 위해 추가 신호 채널을 닫습니다.。
코드 예:
- package main
-
- import (
- "log"
- "math/rand"
- "strconv"
- "sync"
- )
-
- func main() {
-
- const Max = 100000
- const NumReceivers = 10
- const NumSenders = 1000
-
- var wt sync.WaitGroup
- wt.Add(NumReceivers)
-
- dataCh := make(chan int)
- stopCh := make(chan struct{})
- // stopCh是一个额外的信号通道。它的发送
- // 者为中间调解者。它的接收者为dataCh
- // 数据通道的所有的发送者和接收者。
- toStop := make(chan string, 1)
- // toStop是一个用来通知中间调解者让其
- // 关闭信号通道stopCh的第二个信号通道。
- // 此第二个信号通道的发送者为dataCh数据
- // 通道的所有的发送者和接收者,它的接收者
- // 为中间调解者。它必须为一个缓冲通道。
-
- var stoppedBy string
-
- // 中间调解者
- go func() {
- stoppedBy = <-toStop
- close(stopCh)
- }()
-
- // 发送者
- for i := 0; i < NumSenders; i++ {
- go func(id string) {
- for {
- value := rand.Intn(Max)
- if value == 0 {
- // 为了防止阻塞,这里使用了一个尝试
- // 发送操作来向中间调解者发送信号。
- select {
- case toStop <- "发送者#" + id:
- default:
- }
- return
- }
-
- select {
- case <-stopCh:
- return
- case dataCh <- value:
- }
- }
- }(strconv.Itoa(i))
- }
-
- // 接收者
- for i := 0; i < NumReceivers; i++ {
- go func(id string) {
- defer wt.Done()
-
- for {
- select {
- case <-stopCh:
- return
- case value := <-dataCh:
- if value == Max {
- // 为了防止阻塞,这里使用了一个尝试
- // 发送操作来向中间调解者发送信号。
- select {
- case toStop <- "接收者:" + id:
- default:
- }
- return
- }
-
- log.Println(value)
- }
- }
- }(strconv.Itoa(i))
- }
-
- wt.Wait()
- log.Println("被" + stoppedBy + "终止了")
-
- }