go channel的使用

channel是goroutine之间通信的管道,可以将值从一个goroutine发送到channel,另一个goroutine从channel接收到这些值。

Do not communicate by sharing memory; instead, share memory by communicating.

创建channel

go 复制代码
//无缓存channel
ch := make(chan int)

//容量为10的缓冲channel
ch := make(chan int, 10)

//指向文件的指针的缓冲通道
cs := make(chan *os.File, 100)

//单向channel
sendCh := make(chan<- int) // 只能发送的通道
recvCh := make(<-chan int)  // 只能接收的通道


len(ch) //返回通道当前长度
cap(ch) // 返回通道缓冲区容量


ch <- v //往通道发送v
v := <-ch //从通道ch接受消息

//关闭通道
close(ch) 

通道其中常见的一种用法

go 复制代码
c := make(chan int)  // Allocate a channel.
// Start the sort in a goroutine; when it completes, signal on the channel.
go func() {
    list.Sort()
    c <- 1  // Send a signal; value does not matter.
}()
doSomethingForAWhile()
<-c   // Wait for sort to finish; discard sent value.

通道(channel)可以包含任何类型的数据,包括其他通道(channels of channels)这种能力允许你创建复杂的并发模式和数据结构。

默认情况下,发送和接收会阻塞,直到另一端准备就绪。这允许 goroutine 在没有显式锁或条件变量的情况下进行同步。

使用 for range遍历通道

for range循环可以持续的从通道中接收数据。

  • 如果通道没有被关闭,且通道中的数据已经被接收完了,for range会一直阻塞等待新的数据发送到通道。
  • 如果通道被关闭,for range循环会继续消费通道中剩余的数据,然后退出循环。
js 复制代码
package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}
        
	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c
	
	fmt.Println(x, y, x+y)

}

使用select处理多个通道

select会阻塞直到某个分支可以执行为止,如果多个分支都可以执行,随机选择一个执行。

如果不想阻塞,可以添加default子句,如果所有相关的通道都没有准备好,select会执行default子句的代码。

每次select只会消费掉一个值,如果希望处理通道中所有的值,需要在循环中使用select.

go 复制代码
package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

关闭channel

close(ch) 关闭通道

关闭通道后,接收方会收到一个信号,表示不会再有新数据到达。

从已关闭的通道读取数据时,会返回通道中的所有剩余数据,直到通道为空。

通常只有通道的发送者(写入数据的一方)应该关闭通道,因为发送者知道何时完成发送操作。

检查通道是否关闭

go 复制代码
v, ok := <-ch
if !ok {
    // 通道已关闭
}

当尝试从已关闭的通道接收数据时,接收操作将成功,但返回值为通道的零值,返回布尔值为false

go 复制代码
package main

import (
	"fmt"
	"time"
)

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

	go func() {
		ch <- "消息"
		close(ch) // 关闭通道
	}()

	for {
		select {
		case msg, ok := <-ch: // ok 是一个布尔值
			if ok {
				fmt.Println("接收到:", msg)
			} else {
				fmt.Println(msg)
				fmt.Println("通道已关闭,未接收到新消息")
			}
		default:
			fmt.Println("没有可用的通道")
		}
		time.Sleep(3 * time.Second)
	}

}

单向通道

单向通道允许通道被限制为只能发送数据或接收数据。可以使代码清晰、避免错误、提高安全性。

  • chan<- T 定义只能往该通道发送数据
  • <-chan T 定义只能从该通道读取数据,只能接收的通道不能用于close操作,close操作是发送操作的一种,用于告诉通道接受者不再有数据发送到通道。

双向通道类型的值chan T可以隐式转换为仅发送类型chan<- T和仅接受类型<-chan T,但不能反过来

go 复制代码
package main

import "fmt"

// 定义一个只能发送的字符串通道pings,将msg发送到pings通道
func ping(pings chan<- string, msg string) {
    pings <- msg
}

// 接收两个参数,一个只能接收的字符串通道 `pings`,和一个只能发送的字符串通道 `pongs`
func pong(pings <-chan string, pongs chan<- string) {
    msg := <-pings
    pongs <- msg
}

func main() {
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)
}

通道信号量的例子

通道可以像信号量一样使用,例如用于限制吞吐量。

go 复制代码
package main

import (
	"fmt"
	"time"
)

var sem = make(chan int, 10)

func Server() {
	for i := 0; i < 100; i++ {
		sem <- 1
		go func() {
			// 模拟业务处理
			fmt.Printf("业务处理中%v \n", i)
			time.Sleep(10 * time.Second)
			<-sem
		}()
	}
}

func main() {
	Server()
}

channel结构

go 复制代码
type hchan struct {
	qcount   uint           // total data in the queue
	dataqsiz uint           // 通道缓冲区大小
	buf      unsafe.Pointer // 指向数据元素数组的指针,实际存储发送到通道中的数据。使用
	elemsize uint16 //每个元素的大小,根据元素类型决定
	closed   uint32  //通道是否关闭标识
	elemtype *_type // element type 道上发送元素的类型
	sendx    uint   // send index 用于指向通道缓冲区中下一个可用的发送位置
	recvx    uint   // receive index 用于指向通道缓冲区中下一个可接收的元素
	recvq    waitq  // 用于存储等待接收数据的 goroutine 的队列。当没有数据可供接收时,接收方会被阻塞并加入此队列。
	sendq    waitq  //用于存储等待发送数据的 goroutine 的队列。当缓冲区满时,发送方会被阻塞并加入此队列。

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex  //互斥锁,用于保护通道的并发访问,确保在多个 goroutine 访问通道时的安全性,防止数据竞争。
}
相关推荐
禁默6 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Code哈哈笑16 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528719 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶19 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_4336184421 分钟前
shell 编程(二)
开发语言·bash·shell
charlie11451419136 分钟前
C++ STL CookBook
开发语言·c++·stl·c++20
袁袁袁袁满36 分钟前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程
ELI_He99942 分钟前
PHP中替换某个包或某个类
开发语言·php
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust