
go语言以其内置的并发原语——goroutine和channel而闻名。goroutine是轻量级的并发执行单元,由go运行时调度,而非操作系统线程。channel则是goroutine之间进行通信和同步的管道,遵循“通过通信共享内存,而不是通过共享内存进行通信”的并发哲学。
当多个Goroutine需要相互传递数据时,Channel提供了类型安全且同步的机制。一个常见的场景是,一个Goroutine(例如“处理者”)需要从多个其他Goroutine(例如“生产者”)接收数据。
在Go中,一个Goroutine可以从一个或多个Channel接收数据。根据业务逻辑的需求,可以选择不同的接收策略。
如果一个Goroutine需要严格按照某种顺序从不同的Channel接收数据,或者需要同时处理来自不同源的特定数量的消息对,可以直接通过连续的接收操作来实现。这种方式是阻塞的,意味着当前Goroutine会等待直到每个Channel都有数据可读。
package main
import "fmt"
import "time"
func producer1(ch chan int) {
time.Sleep(100 * time.Millisecond) // 模拟生产时间
ch <- 10
}
func producer2(ch chan int) {
time.Sleep(50 * time.Millisecond) // 模拟生产时间
ch <- 20
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go producer1(ch1)
go producer2(ch2)
// Goroutine将顺序接收来自ch1和ch2的数据
// 它会先阻塞直到ch1有数据,然后阻塞直到ch2有数据
val1 := <-ch1
val2 := <-ch2
fmt.Printf("顺序接收:来自ch1的值 %d,来自ch2的值 %d\n", val1, val2)
}在上述示例中,main Goroutine会等待ch1和ch2都有数据,然后分别获取它们。这种方式适用于需要配对处理来自不同源的数据的场景。
立即学习“go语言免费学习笔记(深入)”;
在更常见的场景中,一个Goroutine可能需要处理来自多个Channel的任意一个消息,而不关心消息的顺序,或者当多个Channel同时有数据时,Go运行时会公平地选择其中一个。这时,select语句就成为了理想的选择。select语句允许一个Goroutine同时监听多个Channel操作,当其中任何一个操作就绪时,select就会执行相应的case分支。
package main
import (
"fmt"
"time"
)
func routineA(out chan string) {
for i := 0; i < 3; i++ {
time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
out <- fmt.Sprintf("来自RoutineA的消息 %d", i+1)
}
close(out) // 发送完毕后关闭Channel
}
func routineB(out chan string) {
for i := 0; i < 2; i++ {
time.Sleep(time.Duration(i+1) * 150 * time.Millisecond)
out <- fmt.Sprintf("来自RoutineB的消息 %d", i+1)
}
close(out) // 发送完毕后关闭Channel
}
func main() {
chA := make(chan string)
chB := make(chan string)
go routineA(chA)
go routineB(chB)
// 使用select语句从多个Channel接收数据
for {
select {
case msgA, ok := <-chA:
if !ok { // Channel已关闭且无更多数据
chA = nil // 将chA设为nil,避免再次尝试从已关闭的Channel读取
fmt.Println("RoutineA已完成发送。")
break
}
fmt.Println("接收到:", msgA)
case msgB, ok := <-chB:
if !ok { // Channel已关闭且无更多数据
chB = nil // 将chB设为nil
fmt.Println("RoutineB已完成发送。")
break
}
fmt.Println("接收到:", msgB)
default:
// 可选:如果所有case都未就绪,则执行default分支。
// 如果没有default,select会阻塞直到某个case就绪。
// fmt.Println("当前无消息,等待中...")
// time.Sleep(50 * time.Millisecond)
}
// 当所有监听的Channel都变为nil时,表示所有生产者都已完成,退出循环
if chA == nil && chB == nil {
break
}
}
fmt.Println("所有消息处理完毕。")
}在上述main Goroutine中,select语句会监听chA和chB。当其中任何一个Channel有数据时,相应的case就会被执行。如果多个case同时就绪,select会随机选择一个执行,保证公平性。select语句是实现Go并发模式(如工作池、超时控制、取消机制)的核心工具。
注意事项:
启科网络商城系统由启科网络技术开发团队完全自主开发,使用国内最流行高效的PHP程序语言,并用小巧的MySql作为数据库服务器,并且使用Smarty引擎来分离网站程序与前端设计代码,让建立的网站可以自由制作个性化的页面。 系统使用标签作为数据调用格式,网站前台开发人员只要简单学习系统标签功能和使用方法,将标签设置在制作的HTML模板中进行对网站数据、内容、信息等的调用,即可建设出美观、个性的网站。
0
Go的Channel设计非常灵活,它天然支持多个Goroutine向同一个Channel发送数据(多写入者),以及多个Goroutine从同一个Channel接收数据(多读取者)。这意味着,通常情况下,你不需要为每个发送者-接收者对创建独立的Channel,一个共享的输入Channel即可满足需求。
例如,多个“生产者”Goroutine可以向同一个commandChannel发送任务,而一个“消费者”Goroutine则从这个commandChannel接收并处理任务。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, commands chan string, wg *sync.WaitGroup) {
defer wg.Done()
for cmd := range commands { // 从共享Channel接收命令
fmt.Printf("Worker %d 正在处理命令: %s\n", id, cmd)
time.Sleep(100 * time.Millisecond) // 模拟处理时间
}
fmt.Printf("Worker %d 退出。\n", id)
}
func main() {
commands := make(chan string)
var wg sync.WaitGroup
// 启动3个worker Goroutine,它们都从同一个commands Channel接收数据
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, commands, &wg)
}
// 多个Goroutine向同一个commands Channel发送数据
go func() {
for i := 0; i < 5; i++ {
commands <- fmt.Sprintf("任务A-%d", i+1)
time.Sleep(50 * time.Millisecond)
}
}()
go func() {
for i := 0; i < 5; i++ {
commands <- fmt.Sprintf("任务B-%d", i+1)
time.Sleep(70 * time.Millisecond)
}
close(commands) // 所有任务发送完毕后关闭Channel
}()
wg.Wait() // 等待所有worker完成
fmt.Println("所有任务已处理完毕。")
}在这个例子中,两个匿名Goroutine向同一个commands Channel发送任务,而三个worker Goroutine则从该Channel竞争性地获取任务并处理。这种模式在构建并发工作池时非常有用。
在更复杂的交互场景中,例如实现请求-响应模式,仅仅发送数据可能不足以满足需求。有时,发送者需要接收来自接收者的特定回复。一种强大的Go语言惯用法是在发送的消息结构体中包含一个“回复Channel”。这样,接收者就可以通过这个回复Channel将结果或状态回传给特定的发送者。
package main
import (
"fmt"
"time"
)
// 定义消息结构体,包含命令和用于回复的Channel
type Command struct {
Cmd string // 命令内容
Reply chan<- int // 用于发送回复的Channel
}
func processorRoutine(in chan Command) {
for cmd := range in {
fmt.Printf("处理器接收到命令: %s\n", cmd.Cmd)
// 模拟处理时间
time.Sleep(100 * time.Millisecond)
// 根据命令处理结果,发送状态码回给请求者
if cmd.Cmd == "doSomething" {
cmd.Reply <- 200 // 成功
} else {
cmd.Reply <- 500 // 失败
}
}
fmt.Println("处理器例程退出。")
}
func requesterRoutine(id int, out chan<- Command) {
// 创建一个临时的回复Channel,只接收int类型
replyCh := make(chan int)
// 构造命令,并将回复Channel作为消息的一部分发送
command := Command{Cmd: fmt.Sprintf("doSomething-%d", id), Reply: replyCh}
out <- command // 发送请求
// 等待并接收处理器的回复
status := <-replyCh
fmt.Printf("请求者 %d 收到回复状态: %d\n", id, status)
close(replyCh) // 关闭回复Channel,表明不再需要它
}
func main() {
commandChannel := make(chan Command) // 主命令Channel
go processorRoutine(commandChannel) // 启动处理器
// 启动多个请求者
for i := 1; i <= 3; i++ {
go requesterRoutine(i, commandChannel)
}
// 等待一段时间,确保所有Goroutine有机会执行
time.Sleep(1 * time.Second)
close(commandChannel) // 关闭主命令Channel,通知处理器退出
time.Sleep(100 * time.Millisecond) // 确保处理器有时间退出
fmt.Println("主程序退出。")
}这种模式的优势在于:
Go语言通过Goroutine和Channel提供了强大且直观的并发编程模型。理解如何有效地利用Channel在Goroutine之间进行通信是构建高性能、高并发应用的关键。无论是简单的顺序接收,还是通过select进行多路复用,亦或是利用携带回复Channel的消息实现复杂的请求-响应模式,Go都提供了简洁而强大的解决方案。在实际开发中,合理选择和组合这些通信模式,将能够帮助开发者构建出结构清晰、可维护性强的并发系统。同时,务必注意Channel的关闭、死锁避免以及缓冲与非缓冲Channel的选择等最佳实践,以确保程序的健壮性。
以上就是Go语言并发实践:Goroutine间的高效通信与模式的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号