(十一)Go 并发实战:自增整数生成器+并发消息发送器+定时器等

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

前言

前面两节(九)深入解析 Go 语言 GMP 模型:并发编程的核心机制(十)Go并发编程详解:锁、WaitGroup、Channel 我们讲完了Go并发的理论知识。

接下来我们需要在实战中使用,并体会这些并发原语的精髓,把握其中的细节,从而更加高效而合理的在我们的实际生产中使用他们,达到提升性能,保证并发安全的目的。

实战1 自增整数生成器

场景描述

自增整数生成器是一个常见的并发编程问题。我们需要一个能够生成唯一整数ID的生成器,并确保它在多线程环境下的线程安全性。

假设我们需要在一个分布式系统中生成全局唯一的任务ID:

  1. 唯一标识符生成器:在分布式系统中,每个节点需要生成唯一的ID,可以使用类似的自增整数生成器来保证唯一性。
go 复制代码
package main

import (
	"fmt"
	"sync"
)

// Generator 结构体,包含一个互斥锁和一个任务ID计数器
type Generator struct {
	mutex  sync.Mutex
	taskID int
}

// NewGenerator 创建一个新的 Generator
func NewGenerator() *Generator {
	return &Generator{taskID: 0}
}

// Generate 以线程安全的方式生成一个新的唯一任务ID
func (g *Generator) Generate() int {
	g.mutex.Lock()
	defer g.mutex.Unlock()
	g.taskID++
	return g.taskID
}

// worker 函数,模拟一个工作线程,接收一个生成器和一个 WaitGroup
func worker(id int, gen *Generator, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 5; i++ {
		taskID := gen.Generate()
		fmt.Printf("Worker %d 生成了任务ID: %d\n", id, taskID)
	}
}

func main() {
	gen := NewGenerator()
	var wg sync.WaitGroup

	// 启动3个工作线程,每个线程调用 worker 函数生成任务ID
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go worker(i, gen, &wg)
	}

	// 使用 WaitGroup 确保所有goroutine完成后主程序才退出
	wg.Wait()
}

代码分析

  1. Generator结构体

    • Generator 结构体包含一个 sync.Mutex 和一个 taskID 计数器。
    • NewGenerator 函数用于创建一个新的 Generator 实例。
  2. Generate方法

    • Generate 方法生成一个新的唯一任务ID,并通过加锁确保线程安全。
    • 使用 mutex.Lock() 加锁,然后将 taskID 自增,并返回自增后的值。
    • 使用 defer mutex.Unlock() 在函数退出时解锁,保证锁的释放。
  3. worker函数

    • worker 函数模拟一个工作线程,接收一个生成器和一个 sync.WaitGroup
    • 在循环中调用 Generate 方法生成任务ID,并打印到标准输出。
  4. main函数

    • 创建一个新的 Generator 实例。
    • 启动3个工作线程,每个线程调用 worker 函数生成任务ID。
    • 使用 sync.WaitGroup 确保所有goroutine完成后主程序才退出。

场景总结

  1. 唯一标识符生成器:在分布式系统中,每个节点需要生成唯一的ID,可以使用类似的自增整数生成器来保证唯一性。
  2. 任务分发器:在任务调度系统中,需要为每个任务分配一个唯一的序号或者ID,可以使用此类生成器来实现。
  3. 数据流处理:在数据流处理中,有时候需要为数据流中的每个数据项分配唯一的标识符或者顺序号,此生成器也可以用来完成这个任务。

通过使用 sync.Mutex 进行加锁操作,我们确保了在并发环境下每次生成的任务ID都是唯一且递增的,解决了数据竞争的问题。

实战2 并发消息发送器

场景描述

并发消息发送器是一种用于在多线程环境下发送通知的机制。通过使用channel和goroutine,可以实现并发消息的发送。

假设我们需要实现一个并发的电子邮件发送系统,在用户注册时发送欢迎邮件:

go 复制代码
package main

import (
	"fmt"
	"time"
)

func SendEmail(user string) <-chan string {
	emailChannel := make(chan string, 1)

	go func() {
		defer close(emailChannel)
		time.Sleep(2 * time.Second) // Simulate email sending delay
		emailChannel <- fmt.Sprintf("Welcome email sent to %s", user)
	}()

	return emailChannel
}

func main() {
	alice := SendEmail("alice")
	bob := SendEmail("bob")

	fmt.Println(<-alice)
	fmt.Println(<-bob)
}

代码分析

  1. SendEmail函数

    • SendEmail 函数返回一个只接收数据的channel(<-chan string)。
    • 创建了一个带缓冲的channel emailChannel,缓冲区大小为1。
    • 在一个新的goroutine中,通过time.Sleep模拟发送电子邮件的延迟,生成欢迎邮件消息并发送到channel emailChannel,然后关闭channel。
  2. main函数

    • main 函数中,调用 SendEmail 函数分别为用户 "alice" 和 "bob" 生成消息发送器。
    • 通过 <-alice<-bob 从各自的channel接收并打印邮件发送结果。

图示解释

  1. Main :主函数,调用SendEmail函数。
  2. SendEmail(alice) / SendEmail(bob) :为用户alicebob创建消息发送器。
  3. Goroutine(alice) / Goroutine(bob):在新goroutine中发送消息。
  4. emailChannel(alice) / emailChannel(bob):通过channel接收消息并返回给主函数。

这个图示展示了并发消息发送器的工作流程,通过goroutine和channel实现异步消息发送,使得主程序可以并发处理多个消息发送任务。

场景总结

这种并发的消息发送器常用于以下场景:

  1. 通知系统:在一个用户注册系统中,可以在用户注册成功后立即发送欢迎通知,确保通知发送过程不会阻塞主流程。
  2. 消息分发系统:在一个消息队列系统中,可以并发地将消息发送给多个订阅者,提高消息发送效率。
  3. 异步任务处理:在一个异步任务处理系统中,可以并发地处理多个任务,并将结果通过channel返回给调用方。

这个定时器例子展示了如何使用Go语言的goroutine和channel实现一个简单的定时器。这个定时器在指定的时间间隔后通过channel发送一个信号,可以用于在并发环境中处理超时操作。

实战3 定时器

场景描述

定时器是一个常见的并发编程模式,用于在一定时间后触发操作。通过使用channel和goroutine,可以实现一个简单的定时器。

假设我们需要实现一个网络请求超时处理系统:

go 复制代码
package main

import (
	"fmt"
	"time"
)

func RequestHandler(timeout time.Duration) chan string {
	result := make(chan string, 1)

	go func() {
		// 假设这里是一个耗时的请求
		time.Sleep(3 * time.Second)

		// 判断 result 是否已经关闭
		select {
		case <-result:
			fmt.Println("Result channel is closed")
		default:
			result <- "Request successful"
		}
	}()

	timer := Timer(timeout)

	go func() {
		select {
		case res := <-result:
			fmt.Println(res)
		case <-timer:
			fmt.Println("Request timed out")
		}
		close(result)
	}()

	return result
}

func Timer(duration time.Duration) chan bool {
	ch := make(chan bool, 1)

	go func() {
		time.Sleep(duration)
		ch <- true
	}()

	return ch
}

func main() {
	RequestHandler(1 * time.Second)
	time.Sleep(6 * time.Second) // 等待程序结束,查看打印结果
}

代码分析

  1. Timer函数

    • Timer 函数返回一个类型为 chan bool 的channel。
    • 在一个新的goroutine中,使用 time.Sleep(duration) 延迟指定的时间,然后向channel ch 发送 true 信号。
  2. RequestHandler函数

    • RequestHandler 函数返回一个类型为 chan string 的channel。
    • 在一个新的goroutine中模拟网络请求延迟,通过 time.Sleep(3 * time.Second) 延迟3秒后向channel result 发送请求成功的消息。
    • 创建一个定时器,超时时间为 timeout
    • 在另一个新的goroutine中使用 select 语句等待 resulttimer 的信号,如果 result 有消息,则打印结果;如果 timer 触发,则打印超时信息。这里的select是一个多通道监听器
  3. main函数

    • 调用 RequestHandler 函数,并设置超时时间为5秒,查看输出。
      • 当我们将超时时间设置为1s时,我们再来看一下输出
    • 使用 time.Sleep(6 * time.Second) 延迟主程序退出,确保可以看到输出。

图示解释

  1. Main :主函数,调用RequestHandler函数。
  2. RequestHandler:处理网络请求和定时器。
  3. NetworkRequest :模拟网络请求延迟,向ResultChannel发送请求结果。
  4. Timer :定时器,向TimeoutChannel发送超时信号。
  5. ResultChannel:用于接收网络请求结果的channel。
  6. TimeoutChannel:用于接收超时信号的channel。

这个图示展示了并发请求处理系统的工作流程,通过goroutine和channel实现异步请求处理和超时控制,使得主程序可以并发处理多个请求并处理超时情况。

场景总结

这种定时器常用于以下场景:

  1. 任务超时处理:在网络请求或者任务处理中,如果某个任务在规定时间内没有完成,则触发超时操作。
  2. 定时任务:在某些情况下,需要定时执行某个任务或操作。
  3. 延迟执行:在某些情况下,需要延迟执行某个操作。

总结

通过以上几个实战例子,我们展示了如何在Go语言中实现并��编程。使用goroutine和channel,可以高效地处理并发任务,并确保线程安全性。

希望本篇文章能够帮助你更好地理解和掌握Go语言的并发编程。

相关推荐
大圣数据星球3 小时前
Fluss 写入数据湖实战
大数据·设计模式·flink
思忖小下4 小时前
梳理你的思路(从OOP到架构设计)_设计模式Template Method模式
设计模式·模板方法模式·eit
向前看-7 小时前
验证码机制
前端·后端
超爱吃士力架8 小时前
邀请逻辑
java·linux·后端
AskHarries10 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion11 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp12 小时前
Spring-AOP
java·后端·spring·spring-aop
我是前端小学生12 小时前
Go语言中的方法和函数
go
TodoCoder12 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚13 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes