go入门篇5
一、协程
1.1 go协程的特点
- 独立的栈空间
- 共享程序的堆空间
- 调度由用户控制
- 协程是轻量级的线程
1.2 协程的简单案例
func showNum(){
for i:= 0; i < 10; i++{
fmt.Println("test" + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func main(){
//这里就开启了协程
go showNum()
for i:= 0; i < 10; i++{
fmt.Println("main" + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
结果:
main0
test0
test1
main1
main2
...
test9
main9
1.2 MPG模式
- M 操作系统中的线程
- P 协程运行所需的上下文环境
- G 协程
1.3 设置运行的cpu个数
func main(){
cpunum := runtime.NumCPU()
fmt.Println("正在运行的cpu个数", cpunum)
//setCpuNum := cpunum - 1
runtime.GOMAXPROCS(3)
fmt.Println(runtime.NumCPU())
}
结果:
正在运行的cpu个数 4
4
1.4 测试有几个协程争夺资源
- go build -race test.go
1.5 全局变量加锁解决资源竞争
- 案例不是很严谨
var(
ticket int = 100
lock sync.Mutex
)
func saleTicket() {
lock.Lock()
for ticket > 0 {
ticket--
fmt.Printf("当前剩余的票数是:%d", ticket)
fmt.Println()
}
if ticket == 0 {
fmt.Println("无票可卖")
}
lock.Unlock()
}
func main(){
for i := 0; i < 12; i++ {
go saleTicket()
}
time.Sleep(time.Second * 5)
}
1.6 解决协程中可能报错的问题
- defer + recocer
func showNum(){
for i := 0 ; i< 10; i++{
time.Sleep(time.Second)
fmt.Println("test输出了数字", i)
}
}
func test(){
defer func(){
err := recover()
if err != nil {
fmt.Println("出现了问题")
}
}()
var mymap map[int]string
mymap[0] = "hahaha"
}
func main() {
go showNum()
go test()
for i := 0 ; i< 10; i++{
time.Sleep(time.Second)
fmt.Println("main输出了数字", i)
}
}
二、channel
2.1 channe的介绍
- 本质是队列
- 数据是先进先出
- 本身是线程安全的,不需要加锁,即多个协程竞争同一管道资源时,不会发生资源争抢的情况
- 通道是有类型的,一个string类型的通道只能存放string类型的数据
2.2 channel中添加数据
type Cat struct{
name string
age int
address string
}
func main(){
var mychannel chan interface{
} = make(chan interface{
}, 3)
fmt.Println(len(mychannel), cap(mychannel))
mychannel <- "jack"
mychannel <- 11
fmt.Println(len(mychannel), cap(mychannel))
var cat1 Cat = Cat{
name: "tom",
age : 6,
address: "陕西",
}
mychannel <- cat1
<- mychannel
<- mychannel
mycat := <- mychannel
fmt.Printf("%T, %v",mycat, mycat)
//如果要读取cat中的属性,需要进行类型断言才可以
res := mycat.(Cat)
fmt.Println()
fmt.Println(res.name)
}
2.3 关闭channel
- 关闭通道不能添加数据,但是可以弹出数据
func main(){
var mychanel chan int = make(chan int, 3)
mychanel <- 32
//关闭通道不能添加数据,但是可以读取数据
close(mychanel)
num := <- mychanel
mychanel <-76
fmt.Println(num)
fmt.Println(len(mychanel), cap(mychanel))
}
2.4 for-range遍历channel
- 遍历channel前必须关闭channel,否则会出错
- 注意添加的数据不能超过channel的容量,不然遍历不会出结果
type Cat struct{
name string
age int
address string
}
func main(){
var mychanel chan int = make(chan int, 10)
mychanel <- 32
mychanel <- 33
mychanel <- 34
mychanel <- 35
mychanel <- 36
//关闭通道不能添加数据,但是可以读取数据
close(mychanel)
for v := range mychanel{
fmt.Println(v)
}
}
2.5 channel和协程的配合
func writeData(writechan chan int){
for i := 0; i < 3; i++{
writechan <- i
fmt.Println("向管道中写了数据",i)
}
close(writechan)
}
func readData(readchan chan int, exitchan chan bool){
for {
v, ok := <- readchan
fmt.Println(ok)
if !ok{
break
}
fmt.Println("从管道中读取了数据:",v)
}
fmt.Println("haha")
exitchan <- true
close(exitchan)
}
func main(){
var writechan chan int = make(chan int, 3)
var exitchan chan bool = make(chan bool, 1)
go writeData(writechan)
go readData(writechan, exitchan)
//使用exitchan来控制主线程的结束运行
val := <- exitchan
fmt.Println(len(exitchan))
if val == true{
fmt.Println("主线程可以结束运行了")
}
fmt.Println()
}
2.6 channel的阻塞机制
- 通道中没有数据读取时会阻塞
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
C:/Users/Administrator/go/src/awesomeProject1/src/view/locak.go:7 +0x3c
- 向通道中写的数据个数大于通道容量会阻塞
2.7 协程求素数
- 多个协程求素数
func putdata(putchan chan int){
for i := 0; i < 80; i++{
putchan <- i
}
close(putchan)
}
func printdata(putchan chan int, printchan chan int, exirchan chan bool){
for {
num, ok := <-putchan
if !ok {
break
}
//判断num是不是素数
if num != 0 && num != 1{
var flag bool = true
for j := 2; j < num; j++{
if num % j == 0 {
flag = false
break
}
}
if flag{
//是素数,放入管道
printchan <- num
fmt.Println("素数:", num)
}
}
}
//判断完所有的素数
fmt.Println("有协程取不到数")
exirchan <- true
}
func main() {
var putchan chan int = make(chan int, 1000)
var printchan chan int = make(chan int, 4000)
var exitchan chan bool = make(chan bool, 4)
go putdata(putchan)
for i := 0; i < 5; i++ {
go printdata(putchan, printchan, exitchan)
}
go func() {
for i := 0; i < 5; i++ {
<-exitchan
}
fmt.Println("主线程结束")
close(printchan)
}()
time.Sleep(time.Second*5)
fmt.Println("主线程退出")
}
2.8 管道设置为只写或者只读
func main() {
//设置为只写
var mychan chan <- int = make(chan int, 3)
//设置为只读
var mychan1 <- chan int = make(chan int, 3)
}
2.9 select读取管道的数据
- 普通的管道遍历前需亚关闭管道,但是使用select就可以不用关闭管道而读取到里面的数据
func main() {
var mychan chan int = make(chan int, 5)
for i :=0 ; i < 5; i++{
mychan <- i
}
flag:
for{
select {
case num := <- mychan:
fmt.Println(num)
default:
fmt.Println("数据都已经读完了")
break flag
}
}
}
result:
0
1
2
3
4
数据都已经读完了
三、反射
3.1 变量,interface,reflect.value之间的转换
3.1.1 interface{}转换为reflect.value
ref := reflect.valueof(b)
3.1.2 reflect.value转换为interface{}
ival := ref.interface()
3.1.3 interface转换为原来的类型
- 使用类型断言
inter.(stu)
3.2 通过反射获取变量的类型
var stu Student = Student{
name:"jack",
age:23,
address: "西安",
}
res := reflect.TypeOf(stu)
3.3 通过反射操作基本变量
func changeType(num interface{
}){
tp := reflect.TypeOf(num)
//va是value类型的
va := reflect.ValueOf(num)
fmt.Println(tp.Name())
fmt.Println(va.Int())
//将value类型的转化成interface类型
iv := va.Interface()
//使用类型断言将interface{}类型转化为int类型
res := iv.(int)
fmt.Printf("%T %v", res, res)
}
func main(){
var num int = 34
changeType(num)
}
3.4 获取结构体的其他信息
- 结构体名称,字段个数,字段的名称,字段对应的值
type Student struct{
name string
age int
address string
}
func main() {
var stu Student = Student{
name: "jack",
age: 23,
address: "西安",
}
ty := reflect.TypeOf(stu)
va := reflect.ValueOf(stu)
//获取结构体的名称
fmt.Println(reflect.TypeOf(stu).Name())
//获取结构体中定义的字段的个数
fmt.Println(reflect.TypeOf(stu).NumField())
//获取结构体中每一个字段的名称,和其对应的值
for i := 0; i < ty.NumField(); i++ {
key := ty.Field(i)
value := va.Field(i)
fmt.Println("key是:",key.Name, "value是:",value)
}
}
3.5 匿名结构体的反射
- 和普通结构体无任何差别
type Student struct{
name string
age int
address string
}
type Primary struct{
Student
hobby string
age int
}
func main() {
var p Primary = Primary{
}
p.Student = Student{
name: "jack",
address: "xian",
age: 23,
}
p.age = 34
tp := reflect.TypeOf(p)
va := reflect.ValueOf(p)
//结构体名称
fmt.Println(tp.Name())
//字段个数
fmt.Println(tp.NumField())
//%#v展示Student的详细信息
fmt.Printf("%#v",tp.Field(0))
fmt.Println()
for i := 0; i < tp.NumField(); i ++{
fmt.Printf("key是:%v\t\t\t,", tp.Field(i).Name)
fmt.Printf("value是:%v\t\t\t",va.Field(i))
fmt.Println()
}
}
3.6 判断是否为struct类型
- p是结构体对象
kind := reflect.TypeOf(p).Kind()
3.7 修改结构体对象中的字段的值
type Student struct{
Aame string
Age int
Address string
}
func main() {
var stu *Student = &Student{
Aame: "jack",
Address: "xian",
Age: 23,
}
//利用反射修改值必须是指针类型
//判断是否是指针类型
if reflect.TypeOf(stu).Kind() != reflect.Ptr{
return
}
va := reflect.ValueOf(stu)
//获取想要修改的字段
address := va.Elem().FieldByName("Address")
if address.Kind() == reflect.String{
address.SetString("new address changan")
}
fmt.Println(stu.Address)
}
3.8 修改普通类型变量的值
var name string = "西安"
value := reflect.ValueOf(&name)
value.Elem().SetString("jack")
fmt.Println(name)
3.9 修改结构体方法的一些参数
3.10 查看并且调用结构体的方法
func main() {
var mycat Cat = Cat{
Name: "hojju",
Age: 13,
Address: "西安",
}
tp := reflect.TypeOf(mycat)
vl := reflect.ValueOf(mycat)
for i := 0; i < tp.NumMethod(); i ++ {
//得到的函数方法是按照函数名的ASCLL码排序的
fmt.Println(tp.Method(i))
}
var params []reflect.Value
params = append(params,reflect.ValueOf("jack"))
params = append(params,reflect.ValueOf(12))
params = append(params,reflect.ValueOf("陕西西安"))
//调用参数
params = vl.Method(1).Call(params)
for val := range params{
fmt.Println(val)
}
}
3.11 得到结构体tag字段的值
type Cat struct {
Name string`json:"qweqename"`
Age int`json:"age"`
Address string`json:"address"`
}
func main() {
var mycat Cat = Cat{
Name: "hojju",
Age: 13,
Address: "西安",
}
tp := reflect.TypeOf(mycat)
name := tp.Field(0).Tag.Get("json")
fmt.Println(name)
}
qweqename
3.12 注意事项
-
用反射修改机构体中某一属性的值的时候,该结构体必须是指针类型
-
修改结构体值的时候,该属性开头字母必须是大写的