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 访问通道时的安全性,防止数据竞争。
}
相关推荐
2402_857589368 分钟前
SpringBoot框架:作业管理技术新解
java·spring boot·后端
一只爱打拳的程序猿26 分钟前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
杨荧28 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
白子寰34 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
王俊山IT1 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。1 小时前
c++多线程
java·开发语言
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
神奇夜光杯1 小时前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
Themberfue1 小时前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·