go 基础知识汇总 RPC

golang 远程debug

1. 在运行程序的机器上安装dlv
2. 查看应用程序的pid
3. 运行 dlv --listen=localhost:52477 --headless=true --api-version=2 attach pid
4. 在本地添加配置 ip为远程ip, port 为 dlv指定的端口

go modules

// 配置export GO111MODULE=ONexport GOPROXY=https://athens.azurefd.netexport GOPROXY=https://goproxy.io(任选其一)使用go mod init 
go mod tidy -v
// go.mod 文件-----------
require <文件版本> <版本号>
replace( 
<原始版本> <版本号> => <替代版本> <版本号> 
.......
)

go generate使用

// 源码中添加注释//go:generate gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go//go:generate gencodec -type GenesisAccount -field-override genesisAccountMarshaling -out gen_genesis_account.go// 使用如下命令来生成文件命令中的输出文件名必须和注释中保持一致go generate --run="gencodec -type GenesisAccount -field-override genesisAccountMarshaling -out gen_genesis_account.go"

go语言环境搭建

curl -o  https://storage.googleapis.com/golang/go语言安装包名称 && \
sudo tar -C /usr/local -xzf go语言安装包名称 && \
rm go语言安装包名称 && \echo 'export PATH=$PATH:/usr/local/go/bin' | sudo tee -a /etc/profile && \echo 'export GOPATH=$HOME/go' | tee -a $HOME/.bashrc && \echo 'export PATH=$PATH:$GOROOT/bin:$GOPATH/bin' | tee -a $HOME/.bashrc && \
mkdir -p $GOPATH/{src,pkg,bin}

go语言工具

	build       compile packages and dependencies
	clean       remove object files and cached files
	doc         show documentation for package or symbol
	env         print Go environment information
	bug         start a bug report
	fix         update packages to use new APIs
	fmt         gofmt (reformat) package sources
	generate    generate Go files by processing source
	get         download and install packages and dependencies	install     compile and install packages and dependencies	list        list packages
	run         compile and run Go program	test        test packages
	tool        run specified go tool	version     print Go version
	vet         report likely mistakes in packages


Additional help topics:

	c           calling between Go and C
	buildmode   build modes	cache       build and test caching
	filetype    file types
	gopath      GOPATH environment variable
	environment environment variables
	importpath  import path syntax
	packages    package lists
	testflag    testing flags
	testfunc    testing functions

go 语言特征

  • 类型后置

  • struct------class

  • type 新建类型

  • func也是类型

  • 非侵入式接口

make vs new

  • make只用于map.slice.channel的初始化,实质是对以上引用类型的底层数据结构进行内存分配 new试用于任意类型,返回该类型的指针,

defer

1. 在统一处理多个return和panic/recover场景下使用defer2. 谨记“Go语言的函数参数传递的都是值(除了map、slice、chan)”这一重要原则,正确的评估defer指定函数的参数值3. defer不影响返回值,除非是map、slice和chan,或者命名返回值4. 执行顺序:先进后出

panic Recover

  • panic() 抛出异常,立即执行defer 中的函数,如果异常被捕捉的话,程序仍可以继续运行,如果没被捕获的会在执行完defer中的内容,并把异常继续上抛,直到被捕获或者进程中断,退出执行。

  • recover() 必须在defer中才有用

defer func() {   if err := recover();err != nil {
      fmt.Println(err) //  err 就是panic传入的参数
   }
   fmt.Println("打印前")
}()
  • 继承:匿名字段

  • 值类型 struct array 字符串 数字类型 bool

  • 引用类型:map slice chanel

  • 类型的零值

chanel:缓存和无缓存

  • 无缓存 同步

  • 缓存 队列 并发限制 信号量

waitgroup
add
done
wait
once.do()
sync.pool
cond(lock)
context

协程

g-p-m(多线程模型) 标记清除(垃圾回收算法) beego gin rpcx(rpc框架) go-redis(Redis驱动) mgo(mongodb 驱动)

类型switch .(type) 类型断言 v,ok := interface.(int,string)

rpcx使用Go实现,适合使用Go语言实现RPC的功能。

● 基于net/rpc,可以将net/rpc实现的RPC项目轻松的转换为分布式的RPC

● 插件式设计,可以配置所需的插件,比如服务发现、日志、统计分析等

● 基于TCP长连接,只需很小的额外的消息头

● 支持多种编解码协议,如Gob、Json、MessagePack、gencode、ProtoBuf等

● 服务发现:服务发布、订阅、通知等,支持多种发现方式如ZooKeeper、Etcd等

● 高可用策略:失败重试(Failover)、快速失败(Failfast)

● 负载均衡:支持随机请求、轮询、低并发优先、一致性 Hash等

● 规模可扩展,可以根据性能的需求增减服务器

● 其他:调用统计、访问日志等

服务治理性RPC框架

服务治理型的 RPC 框架有 Dubbo、DubboX、Motan 等,这类的 RPC 框架的特点是功能丰富,提供高性能的远程调用以及服务发现及治理功能,适用于大型服务的微服务化拆分以及管理,对于特定语言(Java)的项目可以十分友好的透明化接入。但缺点是语言耦合度较高,跨语言支持难度较大。

跨语言调用性框架

跨语言调用型的 RPC 框架有 Thrift、gRPC、Hessian、Hprose 等,这一类的 RPC 框架重点关注于服务的跨语言调用,能够支持大部分的语言进行语言无关的调用,非常适合于为不同语言提供通用远程服务的场景。但这类框架没有服务发现相关机制,实际使用时一般需要代理层进行请求转发和负载均衡策略控制

string 和 hex 互转

package mainimport (	"fmt"
	"flag")func main() {


	hex := flag.String("inputString","",`请输入要转换的字符串`)
	str := flag.String("hexString","",`请输入要转换的hex`)

	flag.Parse()
 	fmt.Println(fmt.Sprintf("%x", *hex));	var dst []byte
	fmt.Sscanf(*str, "%X", &dst)
	fmt.Println(string(dst))

}

gin 简介

import (    "gopkg.in/gin-gonic/gin.v1"
    "net/http")func main(){

    router := gin.Default()

    router.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello World")
    })
    router.Run(":8000")
}
  • restful路由

1. 路由参数func main(){
    router := gin.Default()

    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })
}

冒号:加上一个参数名组成路由参数。可以使用c.Param 方法读取其值。 // 除了: gin还提供了*号处理参数,*号能匹配的规则就更多。

客户端向服务器发送请求,除了路由参数,其他的参数无非两种,查询字符串query string报文体body参数。所谓query string,即路由用,用?以后连接的key1=value2&key2=value2的形式的参数。当然这个key-value是经过urlencode编码

2. query stringfunc main(){
    router := gin.Default()
    router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname")

        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
  router.Run()
}

使用c.DefaultQuery方法读取参数,其中当参数不存在的时候,提供一个默认值。使用Query方法读取正常参数,当参数不存在的时候,返回空字串

3. 报文体bodyfunc main(){
    router := gin.Default()
    router.POST("/form_post", func(c *gin.Context) {
        message := c.PostForm("message")
        nick := c.DefaultPostForm("nick", "anonymous")

        c.JSON(http.StatusOK, gin.H{            "status":  gin.H{                "status_code": http.StatusOK,                "status":      "ok",
            },            "message": message,            "nick":    nick,
        })
    })
}

http的报文体传输数据就比query string稍微复杂一点,常见的格式就有四种。例如application/jsonapplication/x-www-form-urlencodedapplication/xmlmultipart/form-data。后面一个主要用于图片上传。json格式的很好理解,urlencode其实也不难,无非就是把query string的内容,放到了body体里,同样也需要urlencode。默认情况下,c.PostFROM解析的是x-www-form-urlencoded或from-data的参数

  • 文件上传

func main(){
    router := gin.Default()

    router.POST("/upload", func(c *gin.Context) {
        name := c.PostForm("name")
        fmt.Println(name)
        file, header, err := c.Request.FormFile("upload")        if err != nil {
            c.String(http.StatusBadRequest, "Bad request")            return
        }
        filename := header.Filename

        fmt.Println(file, err, filename)

        out, err := os.Create(filename)        if err != nil {
            log.Fatal(err)
        }        defer out.Close()
        _, err = io.Copy(out, file)        if err != nil {
            log.Fatal(err)
        }
        c.String(http.StatusCreated, "upload successful")
    })
    router.Run(":8000")
}

使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。一种方式是直接返回。然后使用os的操作,把文件数据复制到硬盘上

%v %#v %+v(带字段名打印结构体) %q(带""打印) %t(打印bool值) %T(打印类型) %d %s %[n]v(复用第n个操作数) %%(字符%) %1..f(字符宽度)

一个slice(切片)由三部分构成:指针、长度、容量 。指针指向slice第一个元素对应的底层数组的地址,切片的长度是切片元素的个数,容量是切片的第一个元素到底层数组的末尾

引用类型vs 传引用

最终我们可以确认的是Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),传递的是地址值的拷贝这样就可以修改原内容数据。是否可以修改原内容数据,和传值、传引用没有必然的关系。在C++中,传引用肯定是可以修改原内容数据的,在Go语言里,虽然只有传值,但是我们也可以修改原内容数据,因为参数是引用类型。这里也要记住,引用类型和传引用是两个概念。再记住,Go里只有传值(值传递)

fmt包scanning类函数(fmt.Sscanf())解析

函数通过扫描已格式化的文本来产生值.

Scan、Scanf 和 Scanln 从 os.Stdin 中读取;Fscan、Fscanf 和 Fscanln 从指定的 io.Reader 中读取; Sscan、Sscanf 和 Sscanln 从实参字符串中读取。Scanln、Fscanln 和 Sscanln 在换行符处停止扫描,且需要条目紧随换行符之后;Scanf、Fscanf 和 Sscanf 需要输入换行符来匹配格式中的换行符;其它函数则将换行符视为空格Scanf、Fscanf 和 Sscanf 根据格式字符串解析实参,类似于 Printf。例如,%x 会将一个整数扫描为十六进制数,而 %v 则会扫描该值的默认表现格式。格式化行为类似于 Printf,但也有如下例外:%p 没有实现%T 没有实现%e %E %f %F %g %G 都完全等价,且可扫描任何浮点数或复数数值%s 和 %v 在扫描字符串时会将其中的空格作为分隔符标记 # 和 + 没有实现
package mainimport (	"fmt"
	"math/rand"
	"sort"
	"sync"
	"sync/atomic"
	"time"

	// "time"
	"strings"
	"unsafe")/**
@author: liyan
@date: 28/09/2018TODO:*/type (
	readOp struct {
		key  int
		resp chan int
	}
	writeOp struct {
		key  int
		val  int
		resp chan bool
	}
)type Human struct {
	name  string
	age   int
	phone string}type Student struct {
	Human  //匿名字段
	school string}type Employee struct {
	Human   //匿名字段
	company string}func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}func main() {	/*var ops int64 = 0
	stateGroutine(&ops)
	time.Sleep(time.Second)
	opsFinal := atomic.LoadInt64(&ops)
	fmt.Println("ops", opsFinal)
	Panic1()
	var a [0]int
	var b = [4]int{4, 3, 2, 1}
	fmt.Println(&a, &b, &ops)*/

	//在human上面定义了一个method

	mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
	sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

	mark.SayHi()
	sam.SayHi()

}// 状态协程func stateGroutine(ops *int64) {

	reads := make(chan *readOp)
	writes := make(chan *writeOp)	// 保存状态的状态协程
	go func() {		var state = make(map[int]int)		for {			select {			case read := <-reads:
				read.resp <- state[read.key]			case write := <-writes:
				state[write.key] = write.val
				write.resp <- true
			}
		}
	}()	for r := 0; r < 100; r++ {		go func() {			for {
				read := &readOp{
					key:  rand.Intn(5),
					resp: make(chan int)}				// 询问
				reads <- read				// 等待应答
				<-read.resp
				atomic.AddInt64(ops, 1)
			}
		}()
	}	for w := 0; w < 10; w++ {		go func() {
			write := &writeOp{
				key:  rand.Intn(5),
				val:  rand.Intn(100),
				resp: make(chan bool)}			// 询问
			writes <- write			// 等待应答
			<-write.resp
			atomic.AddInt64(ops, 1)
		}()
	}
}// 冒泡排序// 思想 :从后向前两两比较,较小的数往前移动,一轮比较下来,最小数被交换到第一的位置func BubbleSort(arr []int) []int {	for i := 0; i < len(arr)-1; i++ {		for j := len(arr) - 1; j > i; j-- {			if arr[j] < arr[j-1] {
				arr[j], arr[j-1] = arr[j-1], arr[j]
			}
		}
	}	return arr
}//冒泡排序Profunc BubbleSortPro(arr []int) []int {	var flag bool
	for i := 0; i < len(arr)-1; i++ {		for j := len(arr) - 1; j > i; j-- {			if arr[j] < arr[j-1] {
				arr[j], arr[j-1] = arr[j-1], arr[j]
				flag = true
			}
		}		if !flag {			break
		}
	}	return arr
}// 选择排序// 基本思想 : 在长度为N的无序数组中,第一次遍历n-1个数,找到最小的数值与第一个元素交换;func selectSort(arr []int) {	for i := 0; i < len(arr)-1; i++ {
		minIndex := i		for j := i + 1; j < len(arr); j++ {			if arr[j] < arr[minIndex] {
				minIndex = j
			}
		}		if minIndex != i {
			arr[minIndex], arr[i] = arr[i], arr[minIndex]
		}
	}
}// 插入排序// 基本思想 : 在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。func InsertionSort(arr []int) {	for i := 0; i < len(arr); i++ {		for j := i + 1; j > 0; j-- {			if arr[j] < arr[j-1] {
				arr[j], arr[j-1] = arr[j-1], arr[j]
			} else {				break
			}
		}
	}
}// 堆排序// 构建最二叉堆(最大堆、最小堆)func makeMinHeap(arr []int) {	for i := (len(arr) - 1) / 2; i >= 0; i-- {
		minHeapFixDown(arr, i, len(arr))
	}
}//  从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2,func minHeapFixDown(arr []int, i int, length int) {
	j := 2*i + 1
	for j < length {		// 向下调整!我们需要将这个数与它的两个儿子 2*i+1 和 2*i+2 比较,并选择较小一个与它交换,交换之后如下。
		if j+1 < length && arr[j+1] > arr[j] { //  1. arr[j+1] < arr[j] 同时修改1.2.排序结果倒序
			j++ // j = 2*i+2
		}		if arr[i] >= arr[j] { // 2. arr[i] <= arr[j] 同时修改1.2.排序结果倒序
			break
		}
		arr[i], arr[j] = arr[j], arr[i]
		i = j // 把刚交换过的子节点 j ,作为父节点继续
		j = 2*i + 1
	}
}func HeapSort(arr []int) []int {
	makeMinHeap(arr)	for i := len(arr) - 1; i > 0; i-- {
		arr[0], arr[i] = arr[i], arr[0]		// i 为一维数组长度,因为最小二叉堆的最小值永远为根节点即arr[0],每次循环会把最小值删除,排序结果为倒序
		minHeapFixDown(arr, 0, i)
	}	return arr
}func BitMath(a int) {

	c := a & 1
	switch c {	case 1:
		fmt.Println("奇数")	case 0:
		fmt.Println("偶数")

	}
}func Abs(n int) {	// n >> 31 获取符号位 n 为正数 d =0 ,为负数时 d = -1
	c := (n ^ (n >> 31)) - (n >> 31)	//	d := n >> 31
	//	s := n ^ -1
	//	fmt.Println(s)
	//	fmt.Println(d)
	fmt.Println(c)
}// 二进制逆序func BinaryRevere(int2 int) {
	fmt.Printf("%b", int2)
	fmt.Println()
	int2 = ((int2 & 0xAAAA) >> 1) | ((int2 & 0x5555) << 1)
	int2 = ((int2 & 0xCCCC) >> 2) | ((int2 & 0x3333) << 2)
	int2 = ((int2 & 0xF0F0) >> 4) | ((int2 & 0x0F0F) << 4)
	int2 = ((int2 & 0xFF00) >> 8) | ((int2 & 0x00FF) << 8)
	fmt.Printf("%b", int2)
}// panic() recovery()// recovery() 只有在defer中才能生效// panic() 只能在当前协程中恢复,否则会杀死进程func Panic1() {	defer func() {		if err := recover(); err != nil {
			fmt.Println("error恢复了2", err)
		}
	}()	var wg sync.WaitGroup
	wg.Add(1)	go func() {
		wg.Done()		defer func() {			if err := recover(); err != nil {
				fmt.Println("error恢复了1", err)
			}
		}()		panic("error")

	}()
	wg.Wait()
	fmt.Println("haha")	panic("error2")
}// unsafe包中三个函数 SizeOf()<返回操作数在内存中的字节大小>, AlignOf()<内存对齐倍数,bool和数字类型与其自身占用字节数相同,其他类型为机器数大小>, OffsetOf()<结构体字段开始位置相对于结构体本身的偏移量> ,一个类型 Pointer(万能指针)// 能和内置类型uintptr和任性类型的指针相互转换// 内置类型uintptr可以进行指针运算。func Unsafe1(a ...int) {	type Unsafe1 struct {
		a int
		b bool
		c []int
	}

	c := Unsafe1{		1,		true,
		[]int{1, 2, 4, 3}}

	pb := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&c)) + unsafe.Offsetof(c.a)))
	*pb = a[0]
	fmt.Println(c.a)
}func SubString(s string, i, j int) string {	// string 是不可变对象即不可修改,但是可以切片
	// return string(s[i:j])
	var b strings.Builder
	b.WriteString("li")
	b.WriteString("yan")	// return b.String()
	var s1 string = "localhost 8081"
	var host, port string
	// 从字符串s1给变量host和port赋值
	// fmt.Scanf() 从标准输入获取值,并赋值给指定变量
	fmt.Sscanf(s1, "%s%s", &host, &port)	return host + ":" + port

}// for range 遍历slice时,i,v 有自己的内存地址,并始终保持不变。 &v != &slice[i]// go + 匿名函数 会产生循环变量的快照问题。func Slice() {
	slice := []int{1, 2, 3, 4}
	map1 := make(map[int]*int)	//	chan1 := make(chan int, 1)
	var wg sync.WaitGroup	var mux sync.Mutex
	i := 0
	for _, value := range slice {		//fmt.Println(&value)

		wg.Add(1)		go func() {
			mux.Lock()
			map1[i] = &value
			i++
			mux.Unlock()
			fmt.Printf("%d\n%d\n", map1[i], &value)

			wg.Done()
		}()
	}
	wg.Wait()
	size := len(map1)
	fmt.Print(size)	for _, v := range map1 {
		fmt.Printf("\n%d\n", *v)
	}
}func DeferCall() {	defer func() {
		fmt.Println("qian")
	}()	defer func() {
		fmt.Println("zhong")
	}()	defer func() {		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()	defer func() {
		fmt.Println("hou")
	}()	panic("panic")
	fmt.Println("hehe")

}// 二维数组(N*N),沿对角线方向,从右上角打印到左下角// 先挖坑,再填数func PrintAray() {	var a = [4][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}
	size := len(a)
	len1 := 2*size - 1 //一共生成几行结果
	for k := 0; k < len1; k++ {		//规律是第K行的元素下标满足 : 列号-行号=size-1-k
		for i := 0; i < size; i++ {			for j := 0; j < size; j++ {				if j-i == size-1-k {
					fmt.Printf("%d ", a[i][j])
				}
			}
		}
		fmt.Println()

	}
}func CirclePrintArray() {	/* 1  2  3  4
	  5  6   7  8
	  9  10  11  12	  13 14  15  16
	{0,0}
	   {1,1}
	      {2,2}
			{3,3}
	*/

	a := [4][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}
	tr, tc := 0, 0
	dr, dc := len(a)-1, len(a[0])-1
	for tr <= dr && tc <= dc {		// 只有一行
		if tr == dr {			for i := tc; i <= dc; i++ {
				fmt.Printf("%d ", a[tr][i])
			}			// 只有一列
		} else if tc == dc {			for i := tr; i <= dr; i++ {
				fmt.Printf("%d ", a[i][tc])
			}			// 多行多列
		} else {			for i := tc; i < dc; i++ {
				fmt.Printf("%d ", a[tr][i])
			}			for i := tr; i < dr; i++ {
				fmt.Printf("%d ", a[i][dc])
			}			for i := dc; i > tc; i-- {
				fmt.Printf("%d ", a[dr][i])
			}			for i := dr; i > tr; i-- {
				fmt.Printf("%d ", a[i][tc])
			}
		}
		tr++
		tc++
		dr--
		dc--
	}

}//  go sync临时对象池  Pool 用于存储临时对象,它将使用完毕的对象存入对象池中,在需要的时候取出来重复使用,目的是为了避免重复创建相同的对象造成 GC 负担过重。其中存放的临时对象随时可能被 GC 回收掉(如果该对象不再被其它变量引用)。  从 Pool 中取出对象时,如果 Pool 中没有对象,将返回 nil,但是如果给 Pool.New 字段指定了一个函数的话,Pool 将使用该函数创建一个新对象返回。  Pool 可以安全的在多个例程中并行使用,但 Pool 并不适用于所有空闲对象,Pool 应该用来管理并发的例程共享的临时对象,而不应该管理短寿命对象中的临时对象,因为这种情况下内存不能很好的分配,这些短寿命对象应该自己实现空闲列表。  Pool 在开始使用之后,不能再被复制。------------------------------type Pool struct {	// 创建临时对象的函数
	New func() interface{}
}// 向临时对象池中存入对象func (p *Pool) Put(x interface{})// 从临时对象池中取出对象func (p *Pool) Get() interface{}

------------------------------------------------------------单次执行  Once 的作用是多次调用但只执行一次,Once 只有一个方法,Once.Do(),向 Do 传入一个函数,这个函数在第一次执行 Once.Do() 的时候会被调用,以后再执行 Once.Do() 将没有任何动作,即使传入了其它的函数,也不会被执行,如果要执行其它函数,需要重新创建一个 Once 对象。  Once 可以安全的在多个例程中并行使用。------------------------------// 多次调用仅执行一次指定的函数 ffunc (o *Once) Do(f func())

------------------------------

// 示例:Oncefunc main() {	var once sync.Once
	onceBody := func() {
		fmt.Println("Only once")
	}
	done := make(chan bool)	for i := 0; i < 10; i++ {		go func() {
			once.Do(onceBody) // 多次调用只执行一次
			done <- true
		}()
	}	for i := 0; i < 10; i++ {
		<-done
	}
}// 输出结果:// Only once------------------------------------------------------------互斥锁  互斥锁用来保证在任一时刻,只能有一个例程访问某对象。Mutex 的初始值为解锁状态。Mutex 通常作为其它结构体的匿名字段使用,使该结构体具有 Lock 和 Unlock 方法。  Mutex 可以安全的在多个例程中并行使用。------------------------------// Locker 接口包装了基本的 Lock 和 UnLock 方法,用于加锁和解锁。type Locker interface {
    Lock()
    Unlock()
}// Lock 用于锁住 m,如果 m 已经被加锁,则 Lock 将被阻塞,直到 m 被解锁。func (m *Mutex) Lock()// Unlock 用于解锁 m,如果 m 未加锁,则该操作会引发 panic。func (m *Mutex) Unlock()------------------------------

// 示例:互斥锁type SafeInt struct {
	sync.Mutex
	Num int}func main() {
	count := SafeInt{}
	done := make(chan bool)	for i := 0; i < 10; i++ {		go func(i int) {
			count.Lock() // 加锁,防止其它例程修改 count
			count.Num += i
			fmt.Print(count.Num, " ")
			count.Unlock() // 修改完毕,解锁
			done <- true
		}(i)
	}	for i := 0; i < 10; i++ {
		<-done
	}
}// 输出结果(不固定):// 2 11 14 18 23 29 36 44 45 45------------------------------------------------------------读写互斥锁  RWMutex 比 Mutex 多了一个“读锁定”和“读解锁”,可以让多个例程同时读取某对象。RWMutex 的初始值为解锁状态。RWMutex 通常作为其它结构体的匿名字段使用。  Mutex 可以安全的在多个例程中并行使用。------------------------------// Lock 将 rw 设置为写锁定状态,禁止其他例程读取或写入。func (rw *RWMutex) Lock()// Unlock 解除 rw 的写锁定状态,如果 rw 未被写锁定,则该操作会引发 panic。func (rw *RWMutex) Unlock()// RLock 将 rw 设置为读锁定状态,禁止其他例程写入,但可以读取。func (rw *RWMutex) RLock()// Runlock 解除 rw 的读锁定状态,如果 rw 未被读锁顶,则该操作会引发 panic。func (rw *RWMutex) RUnlock()// RLocker 返回一个互斥锁,将 rw.RLock 和 rw.RUnlock 封装成了一个 Locker 接口。func (rw *RWMutex) RLocker() Locker------------------------------------------------------------组等待  WaitGroup 用于等待一组例程的结束。主例程在创建每个子例程的时候先调用 Add 增加等待计数,每个子例程在结束时调用 Done 减少例程计数。之后,主例程通过 Wait 方法开始等待,直到计数器归零才继续执行。------------------------------

// 计数器增加 delta,delta 可以是负数。func (wg *WaitGroup) Add(delta int)// 计数器减少 1func (wg *WaitGroup) Done()// 等待直到计数器归零。如果计数器小于 0,则该操作会引发 panic。func (wg *WaitGroup) Wait()------------------------------

// 示例:组等待func main() {
	wg := sync.WaitGroup{}
	wg.Add(10)	for i := 0; i < 10; i++ {		go func(i int) {			defer wg.Done()
			fmt.Print(i, " ")
		}(i)
	}
	wg.Wait()
}// 输出结果(不固定):// 9 3 4 5 6 7 8 0 1 2------------------------------------------------------------条件等待  条件等待通过 Wait 让例程等待,通过 Signal 让一个等待的例程继续,通过 Broadcast 让所有等待的例程继续。  在 Wait 之前应当手动为 c.L 上锁,Wait 结束后手动解锁。为避免虚假唤醒,需要将 Wait 放到一个条件判断循环中。官方要求的写法如下:c.L.Lock()for !condition {
    c.Wait()
}// 执行条件满足之后的动作...c.L.Unlock()  Cond 在开始使用之后,不能再被复制。------------------------------type Cond struct {
    L Locker // 在“检查条件”或“更改条件”时 L 应该锁定。} 

// 创建一个条件等待func NewCond(l Locker) *Cond// Broadcast 唤醒所有等待的 Wait,建议在“更改条件”时锁定 c.L,更改完毕再解锁。func (c *Cond) Broadcast()// Signal 唤醒一个等待的 Wait,建议在“更改条件”时锁定 c.L,更改完毕再解锁。func (c *Cond) Signal()// Wait 会解锁 c.L 并进入等待状态,在被唤醒时,会重新锁定 c.Lfunc (c *Cond) Wait()------------------------------

// 示例:条件等待func main() {
	condition := false // 条件不满足
	var mu sync.Mutex
	cond := sync.NewCond(&mu)	// 让例程去创造条件
	go func() {
		mu.Lock()
		condition = true // 更改条件
		cond.Signal()    // 发送通知:条件已经满足
		mu.Unlock()
	}()
	mu.Lock()	// 检查条件是否满足,避免虚假通知,同时避免 Signal 提前于 Wait 执行。
	for !condition {		// 等待条件满足的通知,如果收到虚假通知,则循环继续等待。
		cond.Wait() // 等待时 mu 处于解锁状态,唤醒时重新锁定。
	}
	fmt.Println("条件满足,开始后续动作...")
	mu.Unlock()
}
错误处理新姿势(io)
type errWriter struct {
    io.Writer
    err error
}func (e *errWriter) Write(buf []byte) (int, error) {    if e.err != nil {        return 0, e.err
    }    var n int
    n, e.err = e.Writer.Write(buf)    return n, nil}func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
    ew := &errWriter{Writer: w}
    fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)    for _, h := range headers {
        fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)
    }
    fmt.Fprint(ew, "\r\n")
    io.Copy(ew, body)    return ew.err
}
-------------------------------------------常规type Header struct {
    Key, Value string}type Status struct {
    Code   int
    Reason string}func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
    _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)    if err != nil {        return err
    }    for _, h := range headers {
        _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)        if err != nil {            return err
        }
    }    if _, err := fmt.Fprint(w, "\r\n"); err != nil {        return err
    }
    _, err = io.Copy(w, body)    return err
}

反射

  • Golang关于类型设计的一些原则

    • 变量包括(type, value)两部分

    • type 包括 static type和concrete type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型

    • 类型断言能否成功,取决于变量的concrete type,而不是static type. 因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.

    • 反射是建立在类型之上的,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说

    • 在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型: (value, type)

    • value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i.  ValueOf(nil) returns the zero 
func ValueOf(i interface{}) Value {...}翻译一下:ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}翻译一下:TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil

通过reflect.Value设置实际变量的值

  • reflect.Value是通过reflect.ValueOf(X)获得的,

    只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值

    ,即:要修改反射类型的对象就一定要保证其值是“addressable”的

var str = `
{
  "product": {
    "jigsawMenuId": 1, 
    "id": 1, 
    "jigsawMenuName": "陪伴"
  },
 "test": {
    "jigsawMenuId": 2, 
    "id": 2, 
    "jigsawMenuName": "陪伴.."
  }
}
`type MenuConf struct {
	JigsawMenuId   int    `json:"jigsawMenuId"`
	ID             int    `json:"id"`
	JigsawMenuName string `json:"jigsawMenuName"`}func TestGetInt(t *testing.T) {
	conf := make([]MenuConf, 0)
	confType := reflect.TypeOf(conf).Elem()
	fmt.Println(reflect.TypeOf(conf))
	fmt.Println(confType)
	mapType := reflect.MapOf(reflect.TypeOf(""), confType)
	newMap := reflect.New(mapType)
	fmt.Println(newMap)
	fmt.Println(newMap.Elem())
	err := json.Unmarshal([]byte(str), newMap.Interface())	if err != nil {
		utils.Error(err)		return
	}
	mapDataValue := newMap.Elem()
	fmt.Println(newMap)
	fmt.Println(mapDataValue)
	mapDataLen := mapDataValue.Len()

	confValue := reflect.ValueOf(&conf).Elem()
	fmt.Println(reflect.ValueOf(&conf))
	fmt.Println(confValue)
	sliceValue := reflect.MakeSlice(confValue.Type(), mapDataLen, mapDataLen*2)
	confValue.Set(sliceValue)	for i, key := range mapDataValue.MapKeys() {
		confValue.Index(i).Set(mapDataValue.MapIndex(key))
	}
	utils.Info(conf)
}// output :=== RUN   TestGetInt
[]main.MenuConf
main.MenuConf
&map[]map[]
&map[product:{1 1 陪伴} test:{2 2 陪伴..}]map[product:{1 1 陪伴} test:{2 2 陪伴..}]
&[]
[]2018/12/03 15:52:33 main_test.go:131: [INFO] [{JigsawMenuId:1 ID:1 JigsawMenuName:陪伴} {JigsawMenuId:2 ID:2 JigsawMenuName:陪伴..}] 
--- PASS: TestGetInt (0.00s)
PASS

unsafe介绍

  • sizeOf

类型大小
bool1个字节
intN, uintN, floatN, complexNN/8个字节(例如float64是8个字节)
int, uint, uintptr1个机器字
*T1个机器字
string2个机器字(data,len)
[]T3个机器字(data,len,cap)
map1个机器字
func1个机器字
chan1个机器字
interface2个机器字(type,value)
var x struct {
    a bool
    b int16
    c []int}32位系统 :Sizeof(x)   = 16  Alignof(x)   = 4Sizeof(x.a) = 1   Alignof(x.a) = 1 Offsetof(x.a) = 0Sizeof(x.b) = 2   Alignof(x.b) = 2 Offsetof(x.b) = 2Sizeof(x.c) = 12  Alignof(x.c) = 4 Offsetof(x.c) = 464位系统 :Sizeof(x)   = 32  Alignof(x)   = 8Sizeof(x.a) = 1   Alignof(x.a) = 1 Offsetof(x.a) = 0Sizeof(x.b) = 2   Alignof(x.b) = 2 Offsetof(x.b) = 2Sizeof(x.c) = 24  Alignof(x.c) = 8 Offsetof(x.c) = 8

在defer函数定义时,对外部变量的引用是有两种方式的,分别是作为函数参数和作为闭包引用。作为函数参数,则在defer定义时就把值传递给defer,并被cache起来;作为闭包引用的话,则会在defer函数真正调用时根据整个上下文确定当前的值。

type number intfunc (n number) print()   { fmt.Println(n) }func (n *number) pprint() { fmt.Println(*n) }func main() {
    var n number

    defer n.print()
    defer n.pprint()
    defer func() { n.print() }()
    defer func() { n.pprint() }()

    n = 3}执行结果是:3330


0
565
上一篇: