Go 언어 코루틴

하나: 코루틴의 간단한 사용

  • 1: go 키워드를 사용하여 함수를 식별하면 해당 함수는 코루틴입니다.
package main

import (
	"fmt"
	"time"
)

func testGoRoutine(routine_num int) {
    
    
	for i := 1; i < 5; i++ {
    
    
		fmt.Printf("routine_num is %d ; i is %d\n", routine_num, i)
		time.Sleep(10 * time.Millisecond)
	}
}

func main() {
    
    
	go testGoRoutine(1)
	go testGoRoutine(2)
	time.Sleep(100 * time.Millisecond)
}
// routine_num is 1 ; i is 1
//routine_num is 2 ; i is 1
//routine_num is 2 ; i is 2
//routine_num is 1 ; i is 2
//routine_num is 1 ; i is 3
//routine_num is 2 ; i is 3
//routine_num is 2 ; i is 4
//routine_num is 1 ; i is 4

2: 채널

  • 역할: 하나의 고루틴이 다른 고루틴과 통신할 수 있게 해주는 채널입니다.
  • 채널 정의: pipline := make(chan 채널 유형)
  • 채널 닫힘: 닫기(파이프라인)
  • 채널에 데이터 보내기: pipline<- data
  • 채널에서 데이터 가져오기: mydata := <-pipline
  • 채널을 닫은 후에도 수신자는 여전히 채널에서 데이터를 가져올 수 있지만 수신된 데이터는 항상 0입니다.
  • 채널에서 데이터를 읽을 때 여러 반환 값이 있을 수 있으며 두 번째 반환 값은 채널이 닫혔는지 여부를 나타낼 수 있습니다. 닫혀 있으면 ok는 false이고 닫히지 않으면 ok는 true입니다.
    • x, 확인 := <-파이프라인
  • 버퍼링 및 버퍼링되지 않은 채널
    • 버퍼링되지 않은 채널
      • 특징: 받는 쪽이 보내는 쪽보다 먼저 준비가 되어 있어야 합니다.
    • 버퍼링된 채널

2.1: 채널의 간단한 사용

package main

import "fmt"

// 发送信息到信道中
func send_data(pipline chan int, data int) {
    
    
	pipline <- data
}

// 在信道中获取信息
func get_data(pipline chan int) int {
    
    
	return <-pipline
}

func main() {
    
    
	// 定义一个信道
	pipline := make(chan int)
	go send_data(pipline, 200)
	resData := get_data(pipline)
	fmt.Println(resData)
}
// 200

2.2: 단방향 채널

package main

import (
	"fmt"
	"time"
)

// Sender 定义只写信道类型
type Sender = chan<- int

// Receiver 定义只读信道类型
type Receiver = <-chan int

func main() {
    
    
	var pipline = make(chan int)

	go func() {
    
    
		var sender Sender = pipline
		fmt.Println("准备发送数据: 100")
		sender <- 100
	}()

	go func() {
    
    
		var receiver Receiver = pipline
		num := <-receiver
		fmt.Printf("接收到的数据是: %d", num)
	}()
	// 主函数sleep,使得上面两个goroutine有机会执行
	time.Sleep(time.Second)
}

2.3: 횡단 채널

  • 채널을 통과하려면 채널이 닫혀 있는지 확인하십시오. 그렇지 않으면 루프가 차단됩니다.
package main

import "fmt"

func addDataToChan(myChan chan int) {
    
    
	for i := 1; i <= 5; i++ {
    
    
		myChan <- i
	}
	// 关闭信道
	close(myChan)
}

func main() {
    
    
	// 定义一个信道
	myChan := make(chan int, 5)

	// 信道中加入值, 然后关闭信道
	addDataToChan(myChan)

	// 遍历信道
	for myChanData := range myChan {
    
    
		fmt.Printf("myChan is %d\n", myChanData)
	}

}
//myChan is 1
//myChan is 2
//myChan is 3
//myChan is 4
//myChan is 5

2.4: 잠금으로서의 채널

package main

import (
	"fmt"
	"time"
)

func increment(ch chan bool, x *int) {
    
    
	// 当前协程在信道存上值,其他的协程就一直阻塞中。
	ch <- true
	*x = *x + 1
	<-ch
}

func main() {
    
    
	// 注意要设置容量为 1 的缓冲信道
	pipline := make(chan bool, 1)

	var x int
	for i := 0; i < 1000; i++ {
    
    
		go increment(pipline, &x)
	}
	
	time.Sleep(time.Second)
	fmt.Println("x 的值:", x)
}

셋: select-case의 사용법

  • 사용 범위: 채널 사용만 지원
  • 기능: select를 실행할 때 데이터를 수신한 채널이 있는 한 모든(가능한 경우) case 표현식을 통과한 다음 select가 종료됩니다.
  • 기능: select의 경우는 순차적이 아닌 무작위로 실행됩니다.
  • 참고: default 문을 작성해 보세요. 작성하지 않고 대소문자가 맞지 않으면 예외가 발생합니다.
  • 기본 사용 사례:
package main

import (
	"fmt"
	"sync"
)

// 定义两个信道,开两个协程, 往信道中追加数据, 测试结果。
func sendDataToChan(inputChan chan int, inputData int, wg *sync.WaitGroup) {
    
    
	defer wg.Done()
	inputChan <- inputData
}

func main() {
    
    
	chan01 := make(chan int, 1)
	chan02 := make(chan int, 1)

	var wg sync.WaitGroup
	wg.Add(2)

	go sendDataToChan(chan01, 1, &wg)
	go sendDataToChan(chan02, 2, &wg)

	wg.Wait()

	select {
    
    
	case outData := <-chan01:
		fmt.Printf("outData is %d\n", outData)
	case outData := <-chan02:
		fmt.Printf("outData is %d\n", outData)
	default:
		fmt.Printf("no data\n")
	}

}

//outData is 2
//outData is 1
  • 채널 제한 시간 설정
package main

import (
	"fmt"
	"time"
)

func sendDataToChan(inputChan chan int, inputData int) {
    
    
	// 增加延时
	time.Sleep(20 * time.Millisecond)
	inputChan <- inputData
}

func delayTime(timeChan chan bool, count int) {
    
    
	// 设置最大延时时间
	time.Sleep(time.Duration(count) * time.Millisecond)
	// 通道追加真值
	timeChan <- true
}

func main() {
    
    
	chan01 := make(chan int, 1)
	chan02 := make(chan int, 1)
	timeChan := make(chan bool, 1)

	go sendDataToChan(chan01, 1)
	go sendDataToChan(chan02, 2)

	// 设置
	go delayTime(timeChan, 10)

	select {
    
    
	case outData := <-chan01:
		fmt.Printf("outData is %d\n", outData)
	case outData := <-chan02:
		fmt.Printf("outData is %d\n", outData)
	case <-timeChan:
		fmt.Printf("time out!!!\n")
	}
}

//time out!!!
  • 채널을 닫아도 select-case 문의 판단에 영향을 미치지 않습니다.
package main

import (
	"fmt"
	"time"
)

func sendDataToChan(inputChan chan int, inputData int) {
    
    
	inputChan <- inputData
}

func main() {
    
    
	chan01 := make(chan int, 1)
	chan02 := make(chan int, 1)

	go sendDataToChan(chan01, 1)
	go sendDataToChan(chan02, 2)

	// 阻塞主函数, 保证执行完成
	time.Sleep(time.Duration(30) * time.Millisecond)

	// 关闭通道
	close(chan01)
	close(chan02)

	select {
    
    
	case outData := <-chan01:
		fmt.Printf("outData is %d\n", outData)
	case outData := <-chan02:
		fmt.Printf("outData is %d\n", outData)
	default:
		fmt.Printf("default\n")
	}
}

//outData is 1
//outData is 2

4: Go 코루틴 풀 구현

package main

import (
	"fmt"
	"time"
)

// Pool 定义一个协程池
type Pool struct {
    
    
	work chan func()   // 任务
	sem  chan struct{
    
    } // 数量
}

// New 用于创建协程池对象
func New(size int) *Pool {
    
    
	return &Pool{
    
    
		work: make(chan func()),
		sem:  make(chan struct{
    
    }, size),
	}
}

// worker 用于执行协程任务
func (p *Pool) worker(task func()) {
    
    
	// 如果某个协程发生了异常, 则此时协程池中就少了一个协程。因此数量上需要移除一个协程。
	defer func() {
    
    
		<-p.sem
	}()
	for {
    
    
		task()
		task = <-p.work
	}
}

// NewTask 协程中增加任务
func (p *Pool) NewTask(task func()) {
    
    
	select {
    
    
	case p.work <- task:
	case p.sem <- struct{
    
    }{
    
    }:
		go p.worker(task)
	}
}

func task() {
    
    
	time.Sleep(2 * time.Second)
	fmt.Println(time.Now())
}

func main() {
    
    
	pool := New(3)
	for i := 1; i <= 6; i++ {
    
    
		pool.NewTask(task)
	}
	// 保证所有的协程都执行完毕
	time.Sleep(6 * time.Second)
}

// 分析:
// 开启第一个协程
// 无缓冲通道work不会走, 只会走sem通道, 开启一个协程A, 执行第一个协程。

// 开启第二个协程
// 无缓冲通道work不会走, 只会走sem通道, 开启一个协程B, 执行第二个协程。

// 开启第三个协程
// 无缓冲通道work不会走, 只会走sem通道, 开启一个协程C, 执行第三个协程。

// 开启第四个协程
// sem通道达到最大值了,两个通道都不会执行, 而是一直阻塞。
// 当有协程执行完成,则会在无缓冲通道work中, 获取到该任务, 然后执行该任务。

// 依次类推。。。。

//2022-09-06 14:00:34.546411 +0800 CST m=+2.000235481
//2022-09-06 14:00:34.546422 +0800 CST m=+2.000246776
//2022-09-06 14:00:34.546444 +0800 CST m=+2.000268540
//2022-09-06 14:00:36.547245 +0800 CST m=+4.001160624
//2022-09-06 14:00:36.547282 +0800 CST m=+4.001197624
//2022-09-06 14:00:36.54726 +0800 CST m=+4.001175658

5: 컨텍스트 컨텍스트의 사용

  • 기능: 코루틴 종료 제어
package main

import (
	"context"
	"fmt"
	"time"
)

func monitor(ctx context.Context, number int) {
    
    
	for {
    
    
		select {
    
    
		// 上下文关闭
		case v := <-ctx.Done():
			fmt.Printf("监控器%v,接收到通道值为:%v,监控结束。\n", number, v)
			return
		default:
			fmt.Printf("监控器%v,正在监控中...\n", number)
			time.Sleep(2 * time.Second)
		}
	}
}

func main() {
    
    
	// 实例化上下文
	ctx, cancel := context.WithCancel(context.Background())

	// 开启多个协程
	for i := 1; i <= 5; i++ {
    
    
		go monitor(ctx, i)
	}

	time.Sleep(1 * time.Second)
	// 关闭所有 goroutine
	cancel()
	time.Sleep(5 * time.Second)
	fmt.Println("主程序退出!!")
}

6: WaitGroup

  • 기능: 주요 기능으로, 실행하기 전에 많은 코루틴이 완료될 때까지 기다립니다.
  • 기능: 코루틴 간의 흐름 제어에 사용됩니다.
package main

import (
	"fmt"
	"sync"
)

func worker(x int, wg *sync.WaitGroup) {
    
    
	// 计数器 -1
	defer wg.Done()
	for i := 0; i < 5; i++ {
    
    
		fmt.Printf("worker %d: %d\n", x, i)
	}
}

func main() {
    
    
	var wg sync.WaitGroup
	// 计数器 +2
	wg.Add(2)
	go worker(1, &wg)
	go worker(2, &wg)
	//阻塞, 直到计数器归0
	wg.Wait()
}

7: 뮤텍스 잠금 및 읽기-쓰기 잠금

  • 뮤텍스:
package main

import (
	"fmt"
	"sync"
)

func add(count *int, wg *sync.WaitGroup, lock *sync.Mutex) {
    
    
	lock.Lock()
	*count = *count + 1
	lock.Unlock()
	wg.Done()
}

func main() {
    
    
	var wg sync.WaitGroup
	// 定义一个互斥锁
	lock := &sync.Mutex{
    
    }
	count := 0
	wg.Add(10000)
	// 开1000个协程
	for i := 1; i <= 10000; i++ {
    
    
		go add(&count, &wg, lock)
	}
	wg.Wait()
	fmt.Println("count 的值为:", count)
}

//count 的值为: 10000
  • 읽기-쓰기 잠금
  • 읽기 잠금을 활성화하기 위한 조건: 모든 코루틴의 쓰기 잠금을 닫아야 합니다.
  • 쓰기 잠금을 활성화하기 위한 조건: 코루틴의 모든 읽기 잠금이 닫힙니다.
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    
    
    lock := &sync.RWMutex{
    
    }
    lock.Lock()

    for i := 0; i < 4; i++ {
    
    
        go func(i int) {
    
    
            fmt.Printf("第 %d 个协程准备开始... \n", i)
            lock.RLock()
            fmt.Printf("第 %d 个协程获得读锁, sleep 1s 后,释放锁\n", i)
            time.Sleep(time.Second)
            lock.RUnlock()
        }(i)
    }

    time.Sleep(time.Second * 2)

    fmt.Println("准备释放写锁,读锁不再阻塞")
    // 写锁一释放,读锁就自由了
    lock.Unlock()

    // 由于会等到读锁全部释放,才能获得写锁
    // 因为这里一定会在上面 4 个协程全部完成才能往下走
    lock.Lock()
    fmt.Println("程序退出...")
    lock.Unlock()
}

Guess you like

Origin blog.csdn.net/qq_41341757/article/details/126727745
Go