golang channel 的众多应用场景123


我们知道 go 中有个很重要的数据结构叫做 channel-通道,通过其特性,我们可以完成很多功能,自然就对应到很多应用场景了。

1.应用场景

channel 的一些应用场景包括不限于下面的这些:

  • 1.同步,用来在多个协程之间的同步操作,如同一时间,只能有一个协程工作,其他协程只能等待,这里也可以对应到同步互斥的场景
  • 2.数据传递,可通过 channel 在多个协程间进行数据传递,比如在 生产者-消费者 模式中使用,生产者向 channel 发送数据,消费者从 channel 中消费数据,也就涉及到对 channel 写-读 操作
  • 3.管道,channel 作为管道,可将数据从一处转移到其他处,在数据流中常见
  • 4.缓冲,在有缓存的 channel 中发送数据,没有读取的情况下,就可以将数据缓存起来,有助于缓解读者压力
  • 5.信号通知,通过 channel 的读写完成信号通知
  • 6.范围迭代,通过 for val := range chan 的操作,当遇到 channel 关闭的时候就会退出迭代,从而完成 channel 数据所有接收
  • 7.并发控制,通过 channel 的有缓存类型,限制一定的并发数量,不至于无限制并发消耗系统资源
  • 8.定时器,有很多标准库的结构体都带有 channel 功能,比如 time.Timer,就是一个定时器,通过定时器定期执行某些任务

以上是涉及到 channel 一些应用场景,以下是应用场景的一些实现,见下面的代码示例。

2.应用场景示例

2.1 并发控制

package main

import (
	"fmt"
	"time"
)

// 通过限制 limit 的缓存数量,决定并发时有多少协程在并行运行
var limit = make(chan struct{}, 3)

/**
Time: 2024-07-13T11:44:01.9047944+08:00, Goroutine: 0 exec i : 0, v: 1
Time: 2024-07-13T11:44:01.9047944+08:00, Goroutine: 11 exec i : 11, v: 12
Time: 2024-07-13T11:44:01.9047944+08:00, Goroutine: 4 exec i : 4, v: 5
Time: 2024-07-13T11:44:02.9200384+08:00, Goroutine: 8 exec i : 8, v: 9
Time: 2024-07-13T11:44:02.9200384+08:00, Goroutine: 9 exec i : 9, v: 10
Time: 2024-07-13T11:44:02.9201398+08:00, Goroutine: 10 exec i : 10, v: 11
Time: 2024-07-13T11:44:03.9207458+08:00, Goroutine: 5 exec i : 5, v: 6
Time: 2024-07-13T11:44:03.9207458+08:00, Goroutine: 1 exec i : 1, v: 2
Time: 2024-07-13T11:44:03.9207458+08:00, Goroutine: 2 exec i : 2, v: 3
Time: 2024-07-13T11:44:04.9240989+08:00, Goroutine: 6 exec i : 6, v: 7
Time: 2024-07-13T11:44:04.9243366+08:00, Goroutine: 3 exec i : 3, v: 4
Time: 2024-07-13T11:44:04.9243366+08:00, Goroutine: 7 exec i : 7, v: 8
Time: 2024-07-13T11:44:07+08:00, 主线程退出!
 */
func main() {
	tasks := []int{1, 2, 3, 4, 5, 6 ,7, 8, 9, 10, 11, 12}
	for i, v := range tasks {
		// 每个task开启一个协程
		go func(i, v int) {
			// 通过chan控制并发
			limit <- struct{}{}

			// 具体的任务执行
			fmt.Printf("Time: %v, Goroutine: %v exec i : %d, v: %v\n", time.Now().Format(time.RFC3339Nano), i, i, v)
			time.Sleep(time.Second)

			<- limit
		}(i, v)
	}

	time.Sleep(6 * time.Second)
	fmt.Printf("Time: %v, 主线程退出!", time.Now().Format(time.RFC3339))
}

2.2 管道 | 范围迭代 | 数据传输

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	numCh := make(chan int)

	for i := 0; i < 5; i++ {
		go sender(numCh)
	}

	for val := range numCh {
		fmt.Println("Main recv val: ", val)
		time.Sleep(time.Second*2)
	}

	fmt.Println("Data transform model Done.")
}

func sender(num chan int)  {
	randomNum := rand.Intn(100)
	num <- randomNum
	fmt.Println("Send val to chan:", randomNum)
	time.Sleep(time.Second)
}

2.3 数据传递 -> 生产者-消费者模型

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

/*
result:
...
生产者持续发送消息:59
消费者持续接收消息:59
生产者持续发送消息:53
生产者关闭通道channel.
消费者持续接收消息:53
消费者接收完成.
本次生产者-消费者模型结束.
 */
func main() {
	var wg sync.WaitGroup
	wg.Add(2)

	numCh := make(chan int, 1)
	go producer(numCh, &wg)
	go consumer(numCh, &wg)

	wg.Wait()
	fmt.Println("本次生产者-消费者模型结束.")
}

func producer(num chan int, wg *sync.WaitGroup)  {
	defer wg.Done()
	count := 0
	for count < 1 {
		for i := 0; i < 3; i++ {
			randomNum := rand.Intn(100)
			num <- randomNum
			fmt.Printf("生产者持续发送消息:%d\n", randomNum)
		}
		time.Sleep(time.Second)
		count++
	}

	close(num)
	fmt.Println("生产者关闭通道channel.")
}

func consumer(num chan int, wg *sync.WaitGroup)  {
	defer wg.Done()
	for val := range num {
		fmt.Printf("消费者持续接收消息:%d\n", val)
		time.Sleep(time.Second)
	}

	fmt.Println("消费者接收完成.")
}

2.4 互斥同步

package main

import (
	"fmt"
	"time"
)

func main() {
	mu := NewMutexLock()
	ok := mu.tryLock()
	fmt.Printf("locked v %v\n", ok) // true
	ok = mu.tryLock()
	fmt.Printf("locked v %v\n", ok) // false,需要等待持锁方释放后才能再获取锁
}

type mutex struct {
	ch chan struct{}
}

func NewMutexLock() *mutex {
	// 定义 1 个 cap 的互斥锁,拿到数据读取到数据就是拿到锁,发送成功数据就是解锁
	mu := &mutex{ch: make(chan struct{}, 1)}
	mu.ch <- struct{}{}
	return mu
}

// 读取到数据就是锁定状态
func (m *mutex) lock()  {
	<- m.ch
}

// 写入数据就是解锁状态
func (m *mutex) unlock()  {
	select {
	case m.ch <- struct{}{}:
	default:
		panic("unlock of unlocked mutex")
	}
}

func (m *mutex) tryLock() bool {
	select {
	case <- m.ch:
		return true
	default:
	}

	return false
}

// 锁超时
func (m *mutex) lockTimeout(timeout time.Duration) bool {
	timer := time.NewTimer(timeout)
	select {
	case <- m.ch:
		timer.Stop()
		return true
	case <- timer.C:
	}

	return false
}

func (m *mutex) isLocked() bool {
	return len(m.ch) == 0
}

2.5 信号通知

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"
)

/*
处理业务中...
处理业务中...
处理业务中...
处理业务中...
处理业务中...
超时了,不等cleanup了。
优雅退出。
*/
func main() {
	var (
		closing = make(chan struct{})
		closed = make(chan struct{})
	)

	go func() {
		// 模拟业务处理
		for {
			select {
			case <- closing:
				return
			default: // 只要没有打断程序运行,这里就会一直执行下去,直到收到打断信号
				fmt.Println("处理业务中...")
				time.Sleep(100 * time.Millisecond)
			}
		}
	}()

	// 处理 ctrl + c 等中断信号
	terminalChan := make(chan os.Signal)
	signal.Notify(terminalChan, syscall.SIGINT, syscall.SIGTERM) // 待接收信号写入 terminalChan
	<- terminalChan // 收到信号,读取信号

	close(closing) // 关闭 closing,发送信号给业务处理 goroutine,让其结束业务

	// 因为已经结束业务,这里做一些清理工作
	go doCleanup(closed) // 清理结束后,会关闭 closed chan

	// 主线程监听 closed 的关闭情况
	select {
	case <- closed:
	case <- time.After(time.Second):
		fmt.Println("超时了,不等cleanup了。")
	}

	fmt.Println("优雅退出。")
}

//
func doCleanup(closed chan struct{})  {
	time.Sleep(5 * time.Second) // 模拟实际的清理
	close(closed) // 关闭 chan
}

2.6 定时器

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"
)

/*
处理业务中...
处理业务中...
处理业务中...
处理业务中...
处理业务中...
超时了,不等cleanup了。
优雅退出。
*/
func main() {
	var (
		closing = make(chan struct{})
		closed = make(chan struct{})
	)

	go func() {
		// 模拟业务处理
		for {
			select {
			case <- closing:
				return
			default: // 只要没有打断程序运行,这里就会一直执行下去,直到收到打断信号
				fmt.Println("处理业务中...")
				time.Sleep(100 * time.Millisecond)
			}
		}
	}()

	// 处理 ctrl + c 等中断信号
	terminalChan := make(chan os.Signal)
	signal.Notify(terminalChan, syscall.SIGINT, syscall.SIGTERM) // 待接收信号写入 terminalChan
	<- terminalChan // 收到信号,读取信号

	close(closing) // 关闭 closing,发送信号给业务处理 goroutine,让其结束业务

	// 因为已经结束业务,这里做一些清理工作
	go doCleanup(closed) // 清理结束后,会关闭 closed chan

	// 主线程监听 closed 的关闭情况
	select {
	case <- closed:
	case <- time.After(time.Second):
		fmt.Println("超时了,不等cleanup了。")
	}

	fmt.Println("优雅退出。")
}

//
func doCleanup(closed chan struct{})  {
	time.Sleep(5 * time.Second) // 模拟实际的清理
	close(closed) // 关闭 chan
}

posted on 2024-07-13 14:48  进击的davis  阅读(191)  评论(0编辑  收藏  举报

导航