当前位置: 首页 > article >正文

Golang 并发机制-3:通道(channels)机制详解

并发编程是一种创建性能优化且响应迅速的软件的强大方法。Golang(也称为 Go)通过通道(channels)这一特性,能够可靠且优雅地实现并发通信。本文将揭示通道的概念,解释其在并发编程中的作用,并提供有关如何通过无缓冲通道和有缓冲通道发送或接收数据的见解。

介绍通道

在Go语言中,通道是一个基本的特性,它支持在gooutine(并发执行线程)之间进行安全和同步的通信。它们充当管道,通过它可以在程序之间传递数据,促进并发程序中的协调和同步。

通道是单向的,这意味着它们既可以用于发送数据(<- chan)也可以用于接收数据(chan <- )。这种单向性有助于在程序间实现清晰和可控的数据流。
在这里插入图片描述

发送和接收数据

1. 非缓存通道

无缓冲通道是一种同时发送和接收数据的通道。当在无缓冲通道上发送值时,发送方将阻塞,直到有相应的接收方准备接收该数据。同样,接收器将阻塞,直到有可用的数据接收。

下面是一个说明使用非缓冲通道的示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int) // Create an unbuffered channel

    go func() {
        ch <- 42 // Send data into the channel
    }()

    // time.Sleep(time.Second) // Give the Goroutine time to execute

    value := <-ch // Receive data from the channel
    fmt.Println("Received:", value)
}

在这个例子中,一个线程将值 42发送到未缓冲的通道 ch.主线程接收它,程序将阻塞,直到发送方和接收方都准备好。

2. 缓存通道

缓冲通道支持使用指定的缓冲区大小异步发送和接收数据。这意味着只要缓冲区未满,就可以在不等待接收器的情况下向通道发送多个值。同样,只要缓冲区不是空的,接收方可以从通道中读取数据,而不需要等待发送方。

下面是一个使用缓冲通道的例子:

package main

import "fmt"

func main() {
    ch := make(chan string, 2) // Create a buffered channel with a capacity of 2

    ch <- "Hello" // Send data into the channel
    ch <- "World"

    fmt.Println(<-ch) // Receive data from the channel
    fmt.Println(<-ch)
}

在本例中,我们创建了容量为2的缓冲通道ch。我们可以在不阻塞的情况下向通道发送两个值,然后接收并打印这些值。当你希望解耦发送方和接收方时,缓冲通道非常有用,允许它们在缓冲区大小约束下独立工作。

  • 同步通道

Go中的通道同步是一种技术,用于通过使用通道来协调和同步Goroutines(并发线程)的执行。通道促进了程序之间安全和有序的通信,允许它们在完成特定任务或准备好数据时相互发送信号。这种同步机制对于确保运行例程以受控和同步的方式执行至关重要。

下面是通道同步有用的一些常见场景:

  1. 等待Goroutines完成:你可以使用通道来等待一个或多个Goroutines在继续主程序之前完成它们的任务。
  2. 协调并行任务:通道可用于协调并发执行任务的多个goroutine,确保它们以特定顺序完成工作或在特定点同步。
  3. 收集结果:通道可用于收集和聚合来自多个goroutine的结果,然后在所有goroutine完成其工作后处理它们。

让我们用例子来探索这些场景:

  • 等待goroute完成
package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d is working\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait() // Wait for all workers to finish
    fmt.Println("All workers have finished.")
}

在这个例子中,我们有三个工作程序。我们使用“同步”。WaitGroup’,等待所有工人完成他们的工作,然后打印“所有工人都完成了”。

  • 协同并行任务
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    ch := make(chan int)

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Goroutine %d is working\n", id)
            ch <- id // Send a signal to the channel when done
        }(i)
    }

    // Wait for all Goroutines to signal completion
    go func() {
        wg.Wait()
        close(ch) // Close the channel when all Goroutines are done
    }()

    for id := range ch {
        fmt.Printf("Received signal from Goroutine %d\n", id)
    }

    fmt.Println("All Goroutines have finished.")
}

在本例中,我们有三个执行工作并使用通道发出完成信号的goroutine。我们使用“同步”。WaitGroup '等待所有的Goroutine完成,并且一个单独的Goroutine侦听通道以知道每个Goroutine何时完成其工作。

  • 收集结果
package main

import (
    "fmt"
    "sync"
)

func worker(id int, resultChan chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    result := id * 2
    resultChan <- result // Send the result to the channel
}

func main() {
    var wg sync.WaitGroup
    resultChan := make(chan int, 3)

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, resultChan, &wg)
    }

    wg.Wait() // Wait for all workers to finish
    close(resultChan) // Close the channel when all results are sent

    for result := range resultChan {
        fmt.Printf("Received result: %d\n", result)
    }
}

在本例中,三个工作线程程序计算结果并将其发送到通道。主程序等待所有工作线程完成,关闭通道,然后从通道读取和处理结果。

这些示例说明了如何使用通道同步来协调和同步Go中各种并发编程场景中的Go例程。通道为线程间安全有序的通信提供了强大的机制,使编写行为可预测且可靠的并发程序变得更加容易。

Select 语句: 多路复用通道(Multiplexing Channels)

管理并发任务的关键工具之一是“select”语句。在本文中,我们将探讨“select”语句在多路复用通道中的作用,这是一种使Go程序员能够有效地同步和协调Go例程的技术。

当你有多个通过各种渠道进行通信的goroutine时,可能需要有效地协调它们的活动。“select”语句支持通过选择首个可以处理的通道操作来实现这一点。

下面是一个简单的例子,演示了在多路信道中使用“select”:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(time.Second)
        ch1 <- "Message from Channel 1"
    }()

    go func() {
        time.Sleep(time.Millisecond * 500)
        ch2 <- "Message from Channel 2"
    }()

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }

    fmt.Println("Main function exits")
}

在本例中,我们有两个在两个不同通道上发送消息的goroutine, ch1 ch2 select语句选择可用的首个通道操作,允许我们从 ch1 ch2接收和打印消息。然后,程序继续执行main函数,演示使用“select”的通道复用功能。

  • select 带缺省分支

‘ select ’语句还支持‘ default ’情况,当您想要处理没有通道操作准备好的情况时,这很有用。这里有一个例子:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan string)

    go func() {
        time.Sleep(time.Second * 2)
        ch <- "Message from Channel"
    }()

    select {
    case msg := <-ch:
        fmt.Println(msg)
    default:
        fmt.Println("No message received")
    }

    fmt.Println("Main function exits")
}

在本例中,我们有一个在通道‘ ch ’上发送消息的程序。然而,“select”语句包含一个“default”情况,用于处理在预期时间内没有消息到达的情况。这允许在没有任何通道操作就绪的情况下进行优雅的处理。

Go中的最佳实践和模式:扇出、扇入和关闭通道

当谈到编写干净高效的Go代码时,有一些最佳实践和模式可以显著提高并发程序的质量和性能。在本文中,我们将探讨两个基本实践:Fan-out, Fan-in(扇出、扇入)和关闭通道。这些模式是在Go应用程序中管理并发性和通信的强大工具。

在这里插入图片描述
在这里插入图片描述
  1. 扇出,扇入

Fan-out, Fan-in模式是一种并发设计模式,允许你跨多个goroutine分发工作,然后收集和合并结果。当处理可以并发处理然后进行聚合的任务时,此模式特别有用。

  • 扇出,扇入示例
package main

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

func worker(id int, input <-chan int, output chan<- int) {
	for number := range input {
		// Simulate some work
		time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
		output <- number * 2
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())

	input := make(chan int)
	output := make(chan int)

	const numWorkers = 3
	var wg sync.WaitGroup

	// Fan-out: Launch multiple workers
	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			worker(id, input, output)
		}(i)
	}

	// Fan-in: Collect results
	go func() {
		wg.Wait()
		close(output)
	}()

	// Send data to workers
	go func() {
		for i := 1; i <= 10; i++ {
			input <- i
		}
		close(input)
	}()

	// Receive and process results
	for result := range output {
		fmt.Println("Result:", result)
	}
}

在本例中,我们创建了三个worker goroutine,它们执行一些模拟工作,然后将结果发送到输出通道。主程序生成输入数据,单独的程序使用扇入模式收集和处理结果。

  1. 关闭通道

关闭通道是发送数据传输完成信号和防止程序无限阻塞的基本做法。当你不再打算通过通道发送数据时,关闭通道以避免死锁是至关重要的。

package main

import "fmt"

func main() {
	dataChannel := make(chan int, 3)

	go func() {
		defer close(dataChannel) // Close the channel when done
		for i := 1; i <= 3; i++ {
			dataChannel <- i
		}
	}()

	// Receive data from the channel
	for num := range dataChannel {
		fmt.Println("Received:", num)
	}
}

在本例中,我们创建了一个容量为3的缓冲通道‘ dataChannel ’。在向通道发送三个值之后,我们使用‘ close ’函数关闭它。关闭任何接收器的通道信号,不再发送数据。这允许接收程序在处理完所有数据后优雅地退出。


http://www.kler.cn/a/527420.html

相关文章:

  • 主流的AEB标准有哪些?
  • 天融信 NGFW2.3 mibs
  • 18、智能驾驶芯片外部接口要求
  • thinkphp6+swoole使用rabbitMq队列
  • Nuxt:利用public-ip这个npm包来获取公网IP
  • 【Rust自学】15.1. 使用Box<T>智能指针来指向堆内存上的数据
  • 【C/C++】区分0、NULL和nullptr
  • 26.Word:创新产品展示说明会【9】
  • Keepalived 安装
  • 基于微信小程序的实习记录系统设计与实现(LW+源码+讲解)
  • DeepSeek的崛起与OpenAI的守擂:AI大模型时代的竞争新格局
  • 自动化数据备份与恢复:让数据安全无忧
  • 动态规划 (环形)
  • Spring的AOP的JoinPoint和ProceedingJoinPoint
  • 网络编程复习
  • 从0开始,来看看怎么去linux排查Java程序故障
  • Day31-【AI思考】-深度学习方法论全解析——科学提升学习效率的终极指南
  • Synology 群辉NAS安装(7)lsof指令和synogear
  • 半导体SAP管理系统:数字化转型的驱动力
  • ComfyUI使用教程、开发指导、资源下载
  • 微服务配置中心 Apollo解析——Portal 关联 Namespace
  • 什么是麦克斯韦方程
  • 2025年01月31日Github流行趋势
  • 3 Spark SQL
  • 【leetcode】T541 (两点反思)
  • 新一代搜索引擎,是 ES 的15倍?