1.初步代码找出问题
package main
import "fmt"
/*
需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中.
最后显示出来。要求使用goroutine完成
思路
1。编写一个函数,来计算各个数的阶乘,并放入到map中.
2。我们启动的协程多个,统计的将结果放入到 map中
3. map应该做出一个全局的.
*/
var (
myMap = make(map[int]int, 10) //预分配十个位置
)
//n阶乘
func test(n int) {
res := 1
for i := 1; i <= n; i++ {
res *= i
}
myMap[n] = res //fatal error: concurrent map writes
//不能多线程同时写入需要加锁
}
func main() {
//开启多个协程
for i := 1; i <= 200; i++ {
go test(i)
}
//输出
for i, v := range myMap {
fmt.Printf("map[%d]=%d", i, v)
}
}
两个问题:
1)多线程同时写入会导致资源竞争,出错。
2)主线程结束导致协程也结束,协程来不及计算。
2.解决办法
1)资源竞争问题,全局变量加锁
在资源写入的时候加锁,用完了解锁。
package main
import (
"fmt"
"sync"
"time"
)
/*
需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中.
最后显示出来。要求使用goroutine完成
思路
1。编写一个函数,来计算各个数的阶乘,并放入到map中.
2。我们启动的协程多个,统计的将结果放入到 map中
3. map应该做出一个全局的.
*/
var (
myMap = make(map[int]int, 10) //预分配十个位置
//声明一个全局的互斥锁
//sync(同步)是一个包,Mutex(互斥)是一个结构体
lock sync.Mutex
)
// n阶乘
func test(n int) {
res := 1
for i := 1; i <= n; i++ {
res *= i
}
//加锁
lock.Lock()
myMap[n] = res //fatal error: concurrent map writes //不能多线程同时写入需要加锁
lock.Unlock()
}
func main() {
//开启多个协程
//200!会超出int范围
for i := 1; i <= 20; i++ {
go test(i)
}
//休眠5s
time.Sleep(time.Second * 5)
//输出
//加锁(底层不知道写入加锁解决问题,加锁防报错)
lock.Lock()
for i, v := range myMap {
fmt.Printf("map[%d]=%d\n", i, v)
}
lock.Unlock()
}
2)管道channel通信
3.channel
1)channel必要性
2)一些特点
1) channle本质就是一个数据结构-队列【示意图
2)数据是先进先出【FIFO : first in first out】
3)线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
4) channel是有类型的,一个string的channel只能存放string类型数据。
3)定义格式
channel(管道)-基本使用定义/声明channel
var变量名chan数据类型
举例:
var intChan chan int (intChan用于存放int数据)
var mapChan chan map[int]string (mapChan用于存放map[int]string类型)
var perChan chan Person
var perChan2 chan *Person..
说明
1) channel是引用类型
2)channel必须初始化才能写入数据,即make后才能使用
3)管道是有类型的,intChan只能写入整数int
package main
import "fmt"
/*
管道
*/
func main() {
//1.创建3个int的管道
var intChan chan int
//slice ,map,chan需要make分配空间
intChan = make(chan int, 3)
//2.看看intchan是什么
fmt.Printf("intChan的值%v,地址%p", intChan, &intChan)
//0xc000094100 引用类型存放地址
//3.管道 写入数据
intChan <- 10
num := 211
intChan <- num
//4.看看管道的长度和容量
fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan)) // len = 2,cap = 3
//(队列)管道长度不能超过容量否则报deadlock,但是可以先取出一个再放入
//5.从管道读数据
var num2 int = <-intChan //从chan取出并返回给变量
fmt.Println("nums =", num2)
fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan)) // len = 2,cap = 3
//6.在没有使用协程的情况下,如果管道数据已经全部取出,再取会报deadlock
//此时管道只有一个值
num3 := <-intChan
//num4 := <-intChan
fmt.Println(num3)
//fmt.Println(num4) //deadlock
}
4)channel使用的注意事项
1. channel中只能存放指定的数据类型
2. channle的数据放满后,就不能再放入了
3.如果从channel取出数据后,可以继续放入
4.在没有使用协程的情况下,如果channel数据取完了,再取,就会报dead lock
5)空接口管道取操作时候的断言
(1)案例
如下图,在空接口管道放入一个结构体,实际类型是结构体,但系统识别是空接口。
取出时表面是空接口类型,实际是结构体类型。在调用结构体字段或者方法的时候就会提示空接口没有这个字段或者方法。
理解:有点像java的子类赋给父类变量,编译通不通过看表面变量类型,运行调用方法看实际类型。
(2) 解决方法:断言
package main
import "fmt"
/*
管道
*/
type Cat struct {
Name string
Age int
}
func main() {
allChan := make(chan interface{}, 3)
allChan <- 10
allChan <- "tom"
cat := Cat{"小花猫", 4}
allChan <- cat
<-allChan
<-allChan
newCat := <-allChan //
fmt.Printf("newCat = %T,newCat = %v\n", newCat, newCat)
//!!!!有点像java的编译看表面类型,运行看实际类型
//fmt.Printf(newCat.Name) //报错,interface{}没有这个属性
//需要类型断言
if data, ok := newCat.(Cat); ok {
fmt.Println("是cat")
fmt.Println(data.Name)
} else {
fmt.Println("不是cat")
}
}