概要
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
Go语言提倡使用通信的方法代替共享内存,这里通信的方法就是使用通道(channel),
管道(channel)特质介绍
(1)管道本质就是一个数据结构-队列
(2)数据是先进先出
(3)自身线程安全,多协程访问时,不需要加锁,channel本身就是线程安全的
(4)管道有类型的,一个string的管道只能存放string类型数据
管道的定义:
var 变量名 chan 数据类型
PS1:chan管道关键字
PS2:数据类型指的是管道的类型,里面放入数据的类型,管道是有类型的,int类型的管道只能写入整数int
PS3:管道是引用类型,必须初始化才能写入数据,即make后才能使用
例如:
package main
import(
"fmt"
)
func main(){
//定义管道 、 声明管道 ---> 定义一个int类型的管道
var intChan chan int
//通过make初始化:管道可以存放3个int类型的数据
intChan = make(chan int,3)
//证明管道是引用类型:
fmt.Printf("intChan的值:%v",intChan) // 0xc000112080
//向管道存放数据:
intChan<- 10
num := 20
intChan<- num
intChan<- 40
//注意:不能存放大于容量的数据:
//intChan<- 80
//在管道中读取数据:
num1 := <-intChan
num2 := <-intChan
num3 := <-intChan
fmt.Println(num1)
fmt.Println(num2)
fmt.Println(num3)
//注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错:
num4 := <-intChan
fmt.Println(num4)
//输出管道的长度:
fmt.Printf("管道的实际长度:%v,管道的容量是:%v",len(intChan),cap(intChan))
}
管道的关闭
使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是任然可以从该管道读取数据
例如:
package main
import(
"fmt"
)
func main(){
//定义管道 、 声明管道
var intChan chan int
//通过make初始化:管道可以存放3个int类型的数据
intChan = make(chan int,3)
//在管道中存放数据:
intChan<- 10
intChan<- 20
//关闭管道:
close(intChan)
//再次写入数据:--->报错
//intChan<- 30
//当管道关闭后,读取数据是可以的:
num := <- intChan
fmt.Println(num)
}
管道的遍历
管道支持for-range的方式进行遍历,有两个细节需要注意
- 在遍历时,如果管道没有关闭,则会出现deadlock的错误
- 在遍历时,如果管道已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
例如:
package main
import(
"fmt"
)
func main(){
//定义管道 、 声明管道
var intChan chan int
//通过make初始化:管道可以存放3个int类型的数据
intChan = make(chan int,100)
for i := 0;i < 100;i++ {
intChan<- i
}
//在遍历前,如果没有关闭管道,就会出现deadlock的错误
//所以我们在遍历前要进行管道的关闭
close(intChan)
//遍历:for-range
for v := range intChan {
fmt.Println("value = ",v)
}
}
协程和管道协同工作案例
【1】案例需求:
请完成协程和管道协同工作的案例,具体要求:
- 开启一个writeData协程,向管道中写入50个整数.
- 开启一个readData协程,从管道中读取writeData写入的数据。
- 注意: writeData和readDate操作的是同一个管道
- 主线程需要等待writeData和readDate协程都完成工作才能退出
原理图:
package main
import(
"fmt"
"time"
"sync"
)
var wg sync.WaitGroup //只定义无需赋值
//写:
func writeData(intChan chan int){
defer wg.Done()
for i := 1;i <= 50;i++{
intChan<- i
fmt.Println("写入的数据为:",i)
time.Sleep(time.Second)
}
//管道关闭:
close(intChan)
}
//读:
func readData(intChan chan int){
defer wg.Done()
//遍历:
for v := range intChan{
fmt.Println("读取的数据为:",v)
time.Sleep(time.Second)
}
}
func main(){//主线程
//写协程和读协程共同操作同一个管道-》定义管道:
intChan := make(chan int,50)
wg.Add(2)
//开启读和写的协程:
go writeData(intChan)
go readData(intChan)
//主线程一直在阻塞,什么时候wg减为0了,就停止
wg.Wait()
}
声明只读只写管道
在管道中,管道可以声明为只读或者只写性质
package main
import(
"fmt"
)
func main(){
//默认情况下,管道是双向的--》可读可写:
//var intChan1 chan int
//声明为只写:
var intChan2 chan<- int // 管道具备<- 只写性质
intChan2 = make(chan int,3)
intChan2<- 20
//num := <-intChan2 报错
fmt.Println("intChan2:",intChan2)
//声明为只读:
var intChan3 <-chan int// 管道具备<- 只读性质
if intChan3 != nil {
num1 := <-intChan3
fmt.Println("num1:",num1)
}
//intChan3<- 30 报错
}