引用类型 – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Fri, 22 May 2026 10:24:15 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 Go学习笔记:Channel基础用法与4种核心注意事项(附代码测试) https://www.shuijingwanwq.com/2026/05/04/9681/ https://www.shuijingwanwq.com/2026/05/04/9681/#respond Mon, 04 May 2026 06:54:54 +0000 https://www.shuijingwanwq.com/?p=9681 Post Views: 184

在Go语言并发编程中,Channel(管道)是Goroutine之间通信的核心机制,它遵循“通过通信来共享内存,而非通过共享内存来通信”的设计哲学,从根本上解决了全局变量加锁同步的诸多隐患。在上一篇学习中,我们通过阶乘案例理解了Channel的必要性,本篇将聚焦Channel的基础定义、初始化方法,重点拆解4种核心使用注意事项,结合代码测试验证每一个细节,记录自己的学习过程与踩坑心得。

一、Channel基础:定义与初始化
在使用Channel之前,必须先明确其定义规范和初始化方法——Channel是引用类型,和map、slice类似,仅声明不初始化无法使用,且必须指定存放的数据类型,只能用于传递该类型的数据。

  1. Channel的定义(声明)格式
    基础定义语法:var 变量名 chan 数据类型
    其中,“数据类型”指定了该Channel能存放的数据种类,不同类型的Channel不能混用。结合实例理解更清晰,常见声明示例如下:
// 1. 声明一个存放int类型数据的Channel
var intChan chan int
// 2. 声明一个存放map[int]string类型数据的Channel
var mapChan chan map[int]string
// 3. 声明一个存放自定义Person类型数据的Channel
type Person struct {
    name string
    age  int
}
var perChan chan Person
// 4. 声明一个存放Person指针类型数据的Channel
var perChan2 chan *Person

注意:仅声明的Channel值为nil,此时无法写入或读取数据,必须通过make函数初始化后才能使用,这是新手最容易踩的第一个小坑。

Channel的初始化与基础使用
Channel的初始化语法:变量名 = make(chan 数据类型, 容量)
其中“容量”(可选,默认无缓冲)表示Channel能存放的数据个数,容量为0时是无缓冲Channel,容量大于0时是有缓冲Channel。

二、Channel核心注意事项(4种场景,附代码测试)
结合学习过程中的测试的,我将Channel的使用注意事项拆解为4种核心场景,每一种都通过“代码示例→测试结果→问题分析”的方式,清晰呈现,避免踩坑。

注意事项1:Channel中只能存放指定类型的数据
Channel在声明时就指定了数据类型,这是固定且严格的——只能向Channel写入该类型的数据,也只能从Channel读取该类型的数据,混用类型会直接编译报错,无法运行。
测试代码(故意混用类型,验证报错):

package main

func main() {
	// 声明并初始化一个存放int类型的Channel
	var intChan chan int
	intChan = make(chan int, 2)

	// (错误操作)
	intChan <- "hello" // 尝试写入string类型数据,此处会编译报错
}

测试结果(编译报错):如图1

/app/go-atguigu/channel-demo # go run main.go
# command-line-arguments
./main.go:12:13: cannot use "hello" (untyped string constant) as int value in send

分析:报错信息明确提示“不能将string类型作为int类型发送到intChan”,这是Channel的基础特性——强类型约束。这样的设计能保证数据传递的安全性,避免不同类型数据混用导致的逻辑错误,尤其在复杂并发场景中,能减少很多潜在问题。

注意事项2:Channel数据放满后,无法再写入数据(会阻塞)
有缓冲Channel的容量是固定的,当Channel中存放的数据个数达到容量上限时,再尝试写入数据,程序会阻塞(暂停执行),直到有其他Goroutine从Channel中读取数据、腾出空闲位置,才能继续写入。如果是无缓冲Channel,写入数据后会直接阻塞,直到有Goroutine读取数据。
测试代码(超出容量写入,验证阻塞):

package main

import (
	"fmt"
)

func main() {

	//演示一下管道的使用
	//1. 创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan = make(chan int, 3)

	//2. 看看intChan是什么
	fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)

	//3. 向管道写入数据
	intChan <- 10
	num := 211
	intChan <- num
	intChan <- 50
	// //如果从channel取出数据后,可以继续放入
	//<-intChan
	intChan <- 98 //注意点, 当我们给管写入数据时,不能超过其容量

}

测试结果(程序阻塞):如图2

/app/go-atguigu/channel-demo # go run main.go
intChan 的值=0x77de188090 intChan本身的地址=0x77de168050
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        /app/go-atguigu/channel-demo/main.go:24 +0x112
exit status 2
/app/go-atguigu/channel-demo # 
测试代码(超出容量写入,验证阻塞)
测试结果(程序阻塞):如图2

分析:因为没有其他Goroutine读取Channel中的数据,写入第4个数据时,Channel已无空闲位置,程序阻塞,最终触发死锁(deadlock)。这里需要注意:无缓冲Channel的容量为0,写入第一个数据就会阻塞,必须有对应的Goroutine读取数据,才能继续执行。

注意事项3:从Channel取出数据后,可以继续写入数据
这是对注意事项2的补充——Channel的容量是固定的,但数据是“动态流转”的:当从Channel中读取一个数据后,Channel的长度会减1,腾出一个空闲位置,此时可以继续写入新的数据,无需担心容量不足(只要不超过初始容量)。
测试代码(读取数据后再写入,验证可行性):

package main

import (
	"fmt"
)

func main() {

	//演示一下管道的使用
	//1. 创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan = make(chan int, 3)

	//2. 看看intChan是什么
	fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)

	//3. 向管道写入数据
	intChan <- 10
	num := 211
	intChan <- num
	intChan <- 50
	// //如果从channel取出数据后,可以继续放入
	<-intChan
	intChan <- 98 //注意点, 当我们给管写入数据时,不能超过其容量

	//4. 看看管道的长度和cap(容量)
	fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3

}

测试结果(正常执行,无报错):

/app/go-atguigu/channel-demo # go run main.go
intChan 的值=0x1ddbd65cc000 intChan本身的地址=0x1ddbd659a030
channel len= 3 cap=3 
/app/go-atguigu/channel-demo # 

分析:从结果可以看出,读取1个数据后,Channel长度从3变为2,腾出1个空闲位置,此时写入新数据(98),长度再次变为3,完全符合预期。这个特性让Channel能够实现“动态流转”,适合用于生产者-消费者模式,平衡数据的产生和消费速度。

注意事项4:无协程时,Channel数据取完后再取,会报deadlock
在没有启动其他Goroutine的情况下,当Channel中的所有数据都被读取完毕后,再尝试读取数据,程序会阻塞,最终触发死锁(deadlock)——因为没有其他Goroutine向Channel中写入新数据,主线程会一直等待,无法继续执行。
测试代码(数据取完后再取,验证死锁):

package main

import (
	"fmt"
)

func main() {

	//演示一下管道的使用
	//1. 创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan = make(chan int, 3)

	//2. 看看intChan是什么
	fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)

	//3. 向管道写入数据
	intChan <- 10
	num := 211
	intChan <- num
	intChan <- 50
	// //如果从channel取出数据后,可以继续放入
	<-intChan
	intChan <- 98 //注意点, 当我们给管写入数据时,不能超过其容量

	//4. 看看管道的长度和cap(容量)
	fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3

	//5. 从管道中读取数据

	var num2 int
	num2 = <-intChan
	fmt.Println("num2=", num2)
	fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 2, 3

	//6. 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock

	num3 := <-intChan
	num4 := <-intChan

	num5 := <-intChan

	fmt.Println("num3=", num3, "num4=", num4, "num5=", num5)

}

测试结果(触发死锁):如图3

测试代码(数据取完后再取,验证死锁)
测试结果(触发死锁):如图3
/app/go-atguigu/channel-demo # go run main.go
intChan 的值=0x2b443f34000 intChan本身的地址=0x2b443f02030
channel len= 3 cap=3 
num2= 211
channel len= 2 cap=3 
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /app/go-atguigu/channel-demo/main.go:41 +0x3bb
exit status 2
/app/go-atguigu/channel-demo # 

分析:主线程读取完Channel中的所有数据后,再尝试读取时,没有其他Goroutine向Channel中写入数据,主线程会一直阻塞在读取操作上,最终导致死锁。这里需要注意:如果有其他Goroutine在后台向Channel写入数据,即使当前Channel为空,读取操作也会等待数据写入,不会立即死锁,这也是Channel实现Goroutine同步的核心原理。

三、总结与学习心得
通过本次学习和代码测试,我彻底掌握了Channel的基础用法和4种核心注意事项,也深刻理解了Channel作为Go并发通信核心的设计逻辑。总结一下重点:

  1. Channel是引用类型,必须先声明、再初始化(make),才能使用;
  2. Channel是强类型的,只能存放和读取指定类型的数据,混用类型会编译报错;
  3. 有缓冲Channel放满后无法继续写入(会阻塞),读取数据后可腾出位置继续写入;
  4. 无协程支持时,Channel数据取完后再取会触发死锁,这是新手最容易踩的坑。

回顾上一篇对“全局变量加锁同步”的学习,结合本次Channel的复习巩固,更能凸显Channel的核心优势——无需手动加锁即可保证并发安全,借助其阻塞特性实现Goroutine之间的精准同步,从根本上规避了手动休眠带来的时间估算难题与资源浪费。此次复习也进一步印证,Go并发编程的核心在于细节把控,即便对Channel的基础用法已有所掌握,重新梳理其容量、类型、读写时机等关键细节,仍能发现此前忽略的知识点,而反复编写测试代码、验证各类场景,正是巩固知识点、规避踩坑的关键。
本次Channel基础用法及注意事项的复习已全部完成,后续将在此基础上,进一步复盘Channel的高级用法,包括无缓冲Channel、单向Channel、select多路复用等,结合生产者-消费者模式等实际场景,查漏补缺、深化理解,确保能够熟练运用Channel实现Goroutine间的高效通信与同步,真正吃透Go并发编程的核心逻辑,夯实自身的技术基础。

]]>
https://www.shuijingwanwq.com/2026/05/04/9681/feed/ 0