Golang入门 - 并发&反射&序列化

文章目录

1.并发

Go语言通过编译器运行时(runtime),从语言上支持了并发的特性。Go语言的并发通过goroutine特性完成。
goroutine类似于线程,但是可以根据需要创建多个goroutine并发工作。goroutine是由Go语言的运行时调度完成,而线程是由操作系统调度完成。

1.1 创建goroutine

func main() {

	go func() {
		for i := 0; i < 100; i++ {
			fmt.Println("hello",i)
		}
	}()

	for i := 0; i < 100; i++ {
		fmt.Println("nihao",i)
	}
}

Golang入门 - 并发&反射&序列化
以上采用匿名函数的方式,也可采用函数的方式,略

1.2 Go语言的协作程序(goroutine)和普通的协作程序(coroutine)

● goroutine可能发生并行执行;但coroutine始终顺序执行。

狭义地说,goroutine可能发生在多线程环境下,goroutine无法控制自己获取高优先度支持;coroutine始终发生在单线程,coroutine程序需要主动交出控制权,宿主才能获得控制权并将控制权交给其他coroutine。

● goroutine间使用channel通信;coroutine使用yield和resume操作。

1.3 goroutine间的通信(channel)

Go语言中的通道(channel)是一种特殊的类型。在任何时候,同时只能有一个goroutine访问通道进行发送和获取数据。goroutine间通过通道就可以通信。就好比一个阻塞队列。

func main() {
ch1 := make(chan int)  //创建存放int型的通道
ch2 := make(chan interface{}) //创建一个空接口类型的通道
ch3 :=make(chan *Student) //创建Student结构体指针类型的通道
}

1.3.1 使用通道发送数据

通道的发送使用特殊的操作符“<-”
通道变量 <- 值

  • 通道变量:通过make创建好的通道实例。
  • 值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。
	ch := make(chan interface{}) //创建一个空接口类型的通道
	ch <- 111
	ch <- "wowouwo"

Golang入门 - 并发&反射&序列化
运行时发现所有的goroutine(包括main)都处于等待goroutine。也就是说所有goroutine中的channel并没有形成发送和接收对应的代码。

1.3.2 使用通道接收数据

  • 通道的收发操作在不同的两个goroutine间进行。
  • 接收将持续阻塞直到发送方发送数据。
  • 每次接收一个元素。
	data := <-ch     //阻塞接受
	data, ok := <-ch //非阻塞接受 ok代表是否接收到数据
	<- ch // 忽略接受
	//执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。
	//这个方式实际上只是通过通道在goroutine间阻塞收发实现并发同步。
	

循环接收

for data:=range ch{
}

通道ch是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过for遍历获得的变量只有一个。

func printer(c chan int) {
	for {
		data := <-c
		if data == 0 {
			break
		}
		fmt.Println(data)
	}
	c <- 0
}

func main() {
	c := make(chan int)
	go printer(c)
	for i := 1; i <= 10; i++ {
		c <- i
	}
	c <- 0
	//等待结束
	<-c
	fmt.Println("结束")
}

单向通道
只能发送的通道类型为chan<-,只能接收的通道类型为<-chan
Golang入门 - 并发&反射&序列化

func main() {
	ch := make(chan int)
	var chSendOnly chan<- int = ch
	data := <-chSendOnly
	fmt.Println(data)
}

Golang入门 - 并发&反射&序列化

	ch := make(chan int)
	//var chSendOnly chan<- int = ch
	//data := <-chSendOnly
	//fmt.Println(data)
	var chReadOnly <-chan int = ch
	<-chReadOnly

Golang入门 - 并发&反射&序列化
带缓冲的通道

Golang入门 - 并发&反射&序列化

1.3.3 通道的多路复用

Go语言中提供了select关键字,可以同时响应多个通道的操作。select的每个case都会对应一个通道的收发过程。当收发完成时,就会触发case中响应的语句。多个操作在每次select中挑选一个进行响应。

func RPCClient(ch chan string, req string) (string, error) {
	ch <- req
	//等待服务器返回
	select {
	case ack := <-ch:
		return ack, nil
	case <-time.After(time.Second):
		return "", errors.New("超时")
	}
}
func RPCServer(ch chan string) {
	for {
		data := <-ch
		fmt.Println("接收到:" + data)
		ch <- "had receive"
	}
	
}

func main() {
	ch := make(chan string)
	go RPCServer(ch)
	ack, err :=
		RPCClient(ch, "hello")
	fmt.Println(ack, err)

}

Golang入门 - 并发&反射&序列化

1.4 同步(lock)

通道内部的实现依然使用了各种锁,因此优雅代码的代价是性能。在某些轻量级的场合,原子访问(atomic包)、互斥锁(sync.Mutex)以及等待组(sync.Wait Group)能最大程度满足需求。
其中介绍等待组:
等待组内部拥有一个计数器,计数器的值可以通过方法调用实现计数器的增加和减少。当我们添加了N个并发任务进行工作时,就将等待组的计数器值增加N。每个任务完成时,这个值减1。同时,在另外一个goroutine中等待这个等待组的计数器值为0时,表示所有任务已经完成。
Golang入门 - 并发&反射&序列化
Golang入门 - 并发&反射&序列化
原子操作:

func main() {
	var c int32
	wg := sync.WaitGroup{}
	//开启100个goroutine
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			tmp := atomic.LoadInt32(&c)
			if !atomic.CompareAndSwapInt32(&c, tmp, tmp+1) {
				fmt.Println("c 修改失败")
			}
		}()
	}
	wg.Wait()
	//c的值有可能不等于100,频繁修改变量值情况下,CompareAndSwap操作有可能不成功。
	fmt.Println("c : ", c)
}

Golang入门 - 并发&反射&序列化
lock:
需要注意的就是:

		lock.Lock()
		defer lock.Unlock()
	var count int
	var lock sync.Mutex
	var arthmatic sync.WaitGroup

	Increment := func() {
		lock.Lock()
		defer lock.Unlock()
		count++
		fmt.Printf("Incrementing: %d\n", count)
	}

	Decrement := func() {
		lock.Lock()
		defer lock.Unlock()
		count--
		fmt.Printf("Decrementing: %d\n", count)
	}

	for i := 0; i < 5; i++ {
		arthmatic.Add(1)
		go func() {
			defer arthmatic.Done()
			Increment()
		}()
	}

	for i := 0; i < 5; i++ {
		arthmatic.Add(1)
		go func() {
			defer arthmatic.Done()
			Decrement()
		}()
	}

	arthmatic.Wait()
	fmt.Printf("Arthmatic completed!\n")

Golang入门 - 并发&反射&序列化

2. 反射

**反射是指在程序运行期对程序本身进行访问和修改的能力。**程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

修改值:

var num int = 10
	rVal := reflect.ValueOf(&num)
	fmt.Printf("%v\n", rVal.Kind())
	rVal.Elem().SetInt(1)

报错

	var str string = "ok"
	rval := reflect.ValueOf(str)
	rval.SetString("oooo")
	fmt.Printf(str)

Golang入门 - 并发&反射&序列化
以下才通过:

var str string = "ok"
	rval := reflect.ValueOf(&str)
	rval.Elem().SetString("oooo")
	fmt.Printf(str)

修改结构体:

type Student struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
}

func (s Student) print() {
	fmt.Println(s)
}

func main() {
	var stu Student = Student{
		Id:   0,
		Name: "zhaosi",
	}
	typeOf := reflect.TypeOf(stu)
	valueOf := reflect.ValueOf(stu)
	kind := valueOf.Kind()
	if kind != reflect.Struct {
		fmt.Println("不是结构体")
		return
	}
	nums := valueOf.NumField()
	fmt.Println("结构体的字段数为:", nums)
	for i := 0; i < nums; i++ {
		fmt.Printf("Field %d:值为: %v\n", i, valueOf.Field(i))
		//获取到标签
		tagval := typeOf.Field(i).Tag.Get("json")
		fmt.Printf("json tag:%v\n", tagval)
	}
	numMethod := valueOf.NumMethod()
	fmt.Printf("结构体有:%v 个方法\n", numMethod)
	//调用结构体的第一个方法
	valueOf.Method(0).Call(nil)
}

3. json (序列化)

结构体:

func main() {
	s := Student{Id: 1, Name: "lisi"}
	data, err := json.Marshal(&s)
	if err != nil {
		fmt.Println("序列化错误")
	}
	fmt.Println(string(data))
}

Golang入门 - 并发&反射&序列化
map :

func testmap() {
	var a  map[string]interface{}
	a=make(map[string]interface{})
	a["a"] = "b"
	a["b"] = 1
	data, err := json.Marshal(a)
	if err != nil {
		fmt.Println("序列化错误")
	}
	fmt.Println(string(data))
}

Golang入门 - 并发&反射&序列化
反序列化

func reversejson() {
	str := "{\"Id\":1,\"Name\":\"lisi\"}"
	var a map[string]interface{}
	//a = make(map[string]interface{}) 不需要make
	err := json.Unmarshal([]byte(str), &a)
	if err != nil {
		fmt.Println("反序列化失败")
	}
	fmt.Println(a)
}

Golang入门 - 并发&反射&序列化

语法入门就差不多了。。。

上一篇:Golang并行处理和内存模型


下一篇:resp.Body.Close() 引发的内存泄漏goroutine个数