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 注意事项

  • 用反射修改机构体中某一属性的值的时候,该结构体必须是指针类型

  • 修改结构体值的时候,该属性开头字母必须是大写的

猜你喜欢

转载自blog.csdn.net/qq_42306803/article/details/120263181