2022/02/13
今天不上班,看得早一点,终于可以在晚上12点前发笔记了,记录下
package main
import "fmt"
/*
channel介绍
1.channel本质就是一个数据结构--队列
2.数据是先进先出,FIFO
3.线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
4.channel是有类型的,一个string的channel只能存放string类型的数据
5.一个channel只能放相同的数据类型
6.channel的数据放满后,就不能再放了
7.在没有使用协程的情况下,如果管道没有数据,再取数据会报错
channel定义
var 变量名 chan 数据类型
demo
var intChan chan int
1.channel是引用类型
2.channel必须初始化才能写入数据,即make后才能使用
3.管道是有类型的,intChan只能写入整数int
*/
func main() {
//1.创建一个可以存放3个int类型的管道
var intChan chan int
intChan = make(chan int, 3)
//管道和map不一样,map容量可以自动增加,但是管道定义的是多少就是多少
//容量不会自动增加
//2.看看intChan是什么
fmt.Println("intChan: ", intChan) //output: intChan: 0xc000152080
fmt.Println("intChan本身的地址: ", &intChan) //output: intChan: 0xc00014c018
//3.向管道写入数据,向管道写数据不能超过容量
intChan <- 10
num := 2
intChan <- num
//4.从管道中读取数据,
//在没有使用协程的情况下,如果管道没有数据,再取数据会报错
var num2 int
num2 = <-intChan
<-intChan //取数据但是不接收,相当于不要这个数据了
fmt.Println("num2 = ", num2) //output: num2 = 10
//5.channel的关闭
//使用内置函数close,关闭管道以后,不能再往channel推入数据,但是可以读取数据
close(intChan)
//intChan <- 300 //panic: send on closed channel
n1 := <-intChan
fmt.Println("n1 = ", n1) //output: n1 = 0
//6.channel的遍历
/*
channel支持for-range的方式进行遍历
1.在遍历时,如果channel没有关闭,则会出现deadlock的错误
2.在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
*/
intChan2 := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan2 <- i * 2
}
//遍历
/*
我们发现,这种方式只推出了50个数据,这是因为每推出一个数据
我们的channel的len就-1
*/
for i := 0; i < len(intChan2); i++ {
fmt.Printf("intChan2[%d] = %v\n", i, <-intChan2)
}
for len(intChan2) > 0 {
fmt.Printf("%v\n", <-intChan2)
}
intChan3 := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan3 <- i * 2
}
//在遍历时,如果channel没有关闭,则会出现deadlock的错误
//会取出来全部的数据,但是没有关闭管道,不知道什么时候是结尾,
//关闭管道提供EOF,遍历到EOF停止
// for v := range intChan3 {//有deadlock
// fmt.Println("v = ", v)
// }
fmt.Println("~~~~下面的是正确的方式~~~~")
close(intChan3)
for v := range intChan3 {
fmt.Println("v = ", v)
}
}
package main
import (
"fmt"
"strconv"
)
/*
1.创建一个mapChan,最多可以存放10个map[string]string的key-val
演示写入和读取
2.创建一个catChan,最多可以存放10个Cat结构体变量,
演示写入和读取的用法
3.创建一个catChan2,最多可以存放10个*Cat变量,
演示写入和读取的用法
4.创建一个allChan,最多可以存放10个任意数据类型变量,
演示写入和读取的用法
//这些题都是一样的,做了两个就可以了-
*/
func test1() {
// 1.创建一个mapChan,最多可以存放10个map[string]string的key-val
// 演示写入和读取
fmt.Println("test1")
mapChan := make(chan map[string]string, 10)
for i := 0; i < 10; i++ {
fmt.Println("开始写入")
myMap := make(map[string]string)
myStr := strconv.Itoa(i)
myMap[myStr] = "hello " + myStr
mapChan <- myMap
}
close(mapChan)
for v := range mapChan {
fmt.Println("取出的v= ", v)
}
}
type Cat struct {
Name string
Age int
}
func main() {
test1()
//定义一个可以存放任意数据类型的管道
//var allChan chan interface{}
allChan := make(chan interface{}, 3)
allChan <- 10
allChan <- "tom jack"
cat := Cat{"小花猫", 4}
allChan <- cat
//我们希望获得到管道中的第三个元素,则先将前两个推出
<-allChan
<-allChan
newCat := <-allChan
fmt.Printf("从管道中取出的cat的类型是: %T, newCat = %v\n", newCat, newCat)
//fmt.Println("newCat.name = %v", newCat.Name) //newCat.Name undefined (type interface {} is interface with no methods)
a, ok := newCat.(Cat) //要用断言,看下这个interface{}变量是否可以转换成我们需要的变量类型
if ok {
fmt.Println(a.Name)
}
}
package main
import (
"fmt"
"time"
)
/*
1.开启一个writeData协程,向管道intChan中写入50个整数
2.开启一个readData协程,从管道intChan中读取writeData写入的数据
3.writeData和readData操作的是同一个管道
4.主线程需要等待writeData和readData协程都完成工作才能退出
如果写入慢而读取快,也不会发生死锁,写入可以慢,但是必须要在写
反之一样
如果写入很快而读很慢,管道容量是30,要写入50个数,会先写入30,
读得慢,就慢慢读,只要检测到在读,慢了也不会死锁
写管道和堵管道的频率不一致,无所谓
如果编译器发现一个管道,只有写,没有读,则该管道写满就会阻塞,死锁
*/
func WriteData(intChan chan int) {
for i := 1; i <= 50; i++ {
//放入数据
intChan <- i
time.Sleep(time.Second)
fmt.Printf("写入数据: %v\n", i)
}
close(intChan)
}
func ReadData(intChan chan int, exitChan chan bool) {
for {
v, ok := <-intChan
if !ok {
break
}
fmt.Printf("读到数据: %v\n", v)
}
//readData读取完数据后,即任务完成
exitChan <- true
close(exitChan)
}
func main() {
intChan := make(chan int, 30)
exitChan := make(chan bool, 1)
go WriteData(intChan)
go ReadData(intChan, exitChan)
//time.Sleep(time.Second * 3)
for {
_, ok := <-exitChan
if !ok { //close会让 ok = false
break
}
//return
}
}
package main
import (
"fmt"
"math/rand"
"strconv"
)
/*
1.创建一个persion结构体,name,age,address
2.使用rand方法配合随机创建10个persion实例,并放入channel中
3.遍历channel,将各个persion实例的信息显示在终端
*/
type Persion struct {
Name string
Age int
Address string
}
func main() {
persionChan := make(chan Persion, 10)
var name string
var age int
var address string
for i := 0; i < 10; i++ {
name = "hello " + strconv.Itoa(rand.Intn(50))
age = rand.Intn(50)
address = "world + " + strconv.Itoa(rand.Intn(50))
per := Persion{name, age, address}
persionChan <- per
}
close(persionChan)
//遍历channel,打印信息
for {
v, ok := <-persionChan
if !ok {
break
}
fmt.Printf("Name = %v, Age = %v, Address = %v\n", v.Name, v.Age, v.Address)
}
}
package main
import (
"fmt"
)
/*
需求,要求统计1-80000数字中,哪些是素数
//传统的方法,就是使用一个循环,循环的判断各个数是不是素数
//使用并发/并行的方式,将统计素数的任务分配个多个(4个)goroutine去完成
完成任务时间短
//这里能非常体会到各个协程是在同时进行!!!
*/
func putNum(intChar chan int) {
for i := 1; i <= 8000; i++ {
intChar <- i
}
//写完了就关闭管道
close(intChar)
}
func primeNum(intChar chan int, primeChan chan int, exitChan chan bool) {
//var num int
var flag bool
for {
//time.Sleep(time.Microsecond)
num, ok := <-intChar
if !ok {
break
}
flag = true
//判断是不是素数
for i := 2; i < num; i++ {
if num%i == 0 { //说明该num不是素数
flag = false
break
}
}
if flag { //如果是素数,就将这个数放入到primeChan
primeChan <- num
}
}
fmt.Println("有一个协程因为取不到数据已经完成工作了")
//这里不能关闭管道,有可能别的协程还在处理
exitChan <- true
}
func main() {
intChan := make(chan int, 100)
primeChan := make(chan int, 2000) //放入结果的channel
exitChan := make(chan bool, 4) //标识退出的管道
//开启一个协程,向intChan放入1-8000个数
go putNum(intChan)
//开启4个协程,从intChan中取出数据,并判断是否为素数
//如果是就放入primeChan
for i := 0; i < 4; i++ {
go primeNum(intChan, primeChan, exitChan)
}
go func() {
for i := 0; i < 4; i++ {
<-exitChan
}
//当我们从exitChan管道取出四个结果,这个时候就说明处理完了
//先关闭保存数据的管道
close(primeChan)
}()
//这里主线程要进行处理
//遍历我们的exitChan 把结果取出
for {
res, ok := <-primeChan //只要在读,无论写得多慢都不影响,所以会阻塞在这
if !ok {
break
}
fmt.Printf("素数都有 %d \n", res)
}
fmt.Println("主线程退出")
}
package main
import "fmt"
/*
1.启动一个协程,将1-1000的数放入到一个numChan中
2.启动8个协程,从numChan中取出数字,并计算1+..+n的值,并存放到resChan
3.最后8个协程完成工作后,再遍历resChan,显示结果
*/
//放入numChan中
func putNum(numChan chan int) {
for i := 1; i <= 100; i++ {
numChan <- i
}
close(numChan) //写完了
}
func getNum(numChan chan int, resChan chan int, exitChan chan bool) {
//读取数据
var retNum int
for {
retNum = 0
v, ok := <-numChan
if !ok {
break
}
for i := 1; i <= v; i++ {
retNum += i
}
resChan <- retNum
}
exitChan <- true //读取完了,在标记的管道上进行标记
}
func main() {
numChan := make(chan int, 500)
resChan := make(chan int, 500)
exitChan := make(chan bool, 8)
go putNum(numChan)
//读取数据,8个协程
for i := 0; i < 8; i++ {
go getNum(numChan, resChan, exitChan)
}
//开启一个匿名协程,任务是找到合适的时机,close放结果的管道
go func() {
//一直在标记管道上读取,直到读到所有标记
for i := 0; i < 8; i++ {
<-exitChan
}
close(resChan)
}()
//主线程读取
for v := range resChan {
fmt.Println("v = ", v)
}
// for {
// v, ok := <-resChan
// if !ok {
// break
// }
// fmt.Println("v = ", v)
// }
}