简介
在 Go 语言中,`channel`是一个很重要的部分,经常在 Go 中的`并发`中使用。
`channel`类似 [unix]() 中的管道(pipe)。
`channel`里的数据遵循`先进先出`的原则,且线程安全,多个`goroutine`访问时,不需要加锁,自己就可以实现线程安全。
channel本身就是线程安全的。
`channel`是有类型的,一个`string`类型的`channel`只能存放`string`类型的数据。
一个int类型的channel只能存放int类型的数据。
你可以通过`make`函数来创建一个`channel`,第二个参数为`channel`的容量,及在同一时间,channel中最多只能存放n个数据,等数据被取出之后,然后可以继续存放数据。
向`channel`写入数据时,不能超过其容量。
当从`channel`读取数据时,如果`channel`中没有数据,则会阻塞当前协程,直到`channel`中有数据可读。
tchannel的特点
- go语言里的管道(channel)类似unix中的管道(pipe)。
- channel里的数据遵循先进先出的原则。
- 不加锁多个goroutine同时操作管道里的数据时不会出现数据冲突。
- channel是有类型的。
channel一般用在什么场景中
-
并发通信:在多个 goroutine 之间进行数据传递和通信。
-
任务分配:将任务分配给不同的 goroutine 进行处理。
-
数据共享:允许不同的 goroutine 访问和修改共享的数据。
-
生产者-消费者模式:一个 goroutine 生产数据,另一个 goroutine 消费数据。
-
异步操作:进行异步的操作,提高程序的性能和响应性。
-
信号传递:用于传递特定的信号或事件。
-
限制并发数:控制同时执行某个操作的 goroutine 数量。
-
线程安全的数据结构:构建线程安全的数据结构。
-
管道式编程:类似于管道的方式处理数据。
-
任务协调:协调多个 goroutine 之间的工作流程。
例如,在生产者-消费者模式中,生产者将数据发送到 channel,而消费者从 channel 中接收数据进行处理。 channel 提供了一种安全的方式在不同的 goroutine 之间进行通信和协调,不需要加锁,是安全的。
如何定义一个channel对象
int类型的管道定义、初始化与写和读数据
package main
import "fmt"
func main() {
var intChan chan int // 定义int类型的管道,chan int为管道的类型
intChan = make(chan int, 4) // 初始化管道的长度
intChan <- 10 // 往管道里写入数据
num := <-intChan // 从管道里读取数据
fmt.Println(num) // 结果为:10
}
字符串类型的管道定义、初始化与写和读操作
package main
import "fmt"
func main() {
var strChan chan string // 定义字符串类型的管道
strChan = make(chan string, 4) // 初始化管道长度
intChan <- "is string" // 往管道里写入数据
str := <-intChan // 从管道里读取数据
fmt.Println(str) // 结果为:is string
}
map类型的管道定义、初始化与写和读操作
package main
import "fmt"
func main() {
var mapChan chan map[string]string
mapChan = make(chan map[string]string, 4)
// 一个map对象
m := make(map[string]string)
m["stu01"] = "001"
m["stu02"] = "002"
mapChan <- m // 将数据写入管道
outm := <-mapChan // 从管道获取数据
fmt.Println(outm)
}
自定义结构体类型的管道定义、初始化与写和读操作
package main
import "fmt"
type Student struct {
Name string
}
func main() {
var stuChan chan *Student
stuChan = make(chan *Student, 10)
stu := Student{Name: "stu01"}
stuChan <- &stu // 写入变量的地址
outstu := <-stuChan // 读取内容(读取的也是变量的地址)
fmt.Println(*outstu) // 打印时需要加*号获取值,否则获取到的是地址
}
interface类型的管道定义、初始化、写、读操作与类型转换
package main
import "fmt"
type Student struct {
Name string
}
func main() {
var stuChan chan interface{}
stuChan = make(chan interface{}, 10)
stu := Student{Name: "interstu"}
stuChan <- &stu
var stu01 interface{}
stu01 = <-stuChan
fmt.Printf("stu01: type[%T],data[%s]\n", stu01, stu01)
// 类型转换(将interface类型的变量stu01转换为Student类型的变量stu02)
var stu02 *Student
// 类型断言判断是否可以转换
stu02, ok := stu01.(*Student)
if !ok { // 如果不可转换打印错误信息
fmt.Println("con not convert", ok)
return
}
fmt.Printf("stu02: type[%T],data[%s]\n", stu02, stu02) // 打印转换结果
}
channel的遍历
channel 支持 for–range 的方式进行遍历,请注意两个细节
1. 在遍历时,如果 channel 没有关闭,则会出现 deadlock 的错误, 因此遍历前需要关闭channel
2. 在遍历时,如果 channel 已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
func main() {
// 定义一个int类型的通道
var intChan chan int
intChan = make(chan int,100)
// 循环往里写数据
for i := 0; i < 100; i++ {
intChan <- i
}
// 如果不关闭则会deadlock
// 关闭了,就不会deadlock
close(intChan)
for v := range intChan{
fmt.Println(v)
}
}
channel的使用注意事项
channel 中只能存放指定的数据类型
channle 的数据放满后,就不能再放入了
如果从 channel 取出数据后,可以继续放入在没有使用协程的情况下,如果 channel 数据取完了,再取,就 会报 dead lock
当管道的类型为任意类型时,从管道获取出来的值必须经过类型断言才能操作或者参与运算
使用select 可以解决从管道取数据的阻塞问题
package main
import (
"fmt"
"time"
)
func main() {
// 使用select可以解决从管道取数据的阻塞问题
// 1.定义一个管道 10个数据int
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
intChan <- i
}
// 2.定义一个管道 5个数据string
stringChan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringChan <- "hello" + fmt.Sprintf("%d", i)
}
//传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock
//问题,在实际开发中,可能我们不好确定什么关闭该管道.
//可以使用select 方式可以解决
//label:
for {
select {
//注意: 这里,如果intChan一直没有关闭,不会一直阻塞而deadlock
//,会自动到下一个case匹配
case v := <-intChan :
fmt.Printf("从intChan读取的数据%d\n", v)
time.Sleep(time.Second)
case v := <-stringChan :
fmt.Printf("从stringChan读取的数据%s\n", v)
time.Sleep(time.Second)
default :
fmt.Printf("都取不到了,不玩了, 可以加入逻辑\n")
time.Sleep(time.Second)
return
//break label
}
}
}
如何设置通道的大小
make(chan data_type, buffer_size)
其中,`data_type`是`channel`中要传递的数据类型,
`buffer_size`表示`channel`的缓冲区大小,如果不指定缓冲区大小,则表示`channel`是无缓冲的。
无缓冲的`channel`可以用于同步操作,例如两个`Go`程之间的阻塞和等待。
有缓冲的`channel`可以用于异步操作,这意味着发送和接收操作不会阻塞程序的执行。
ch := make(chan string,10)
在这里,创建了一个传递字符串类型的`channel`,设置缓冲区大小为10。
有缓冲的`channel`可以用于异步操作,这意味着发送和接收操作不会阻塞程序的执行。
需要注意的是,`channel`的缓冲区大小需要根据实际情况进行设置。
如果缓冲区大小设置得过小,可能会导致频繁的阻塞和等待,从而影响程序的性能。
如果缓冲区大小设置得过大,则会浪费内存资源。
因此,在设置`channel`的缓冲区大小时,需要综合考虑程序的性能和内存使用情况。