最近开始了golang的学习,在这篇博客中记录一下知识点,有利于以后温故知新
大道至简,少即是多
-
iota的用法
在go中,iota为特殊常量,多用于枚举值,第一个iota等于0,每当iota在新的一行被使用时,它的值都会自动加1
package main import "fmt" func main() { const ( a = iota // 0 b // 1 c //2 d = "ha" //独立值,iota += 1 e //"ha" iota += 1 f = 100 //iota +=1 g //100 iota +=1 h = iota //7,恢复计数 i //8 ) fmt.Println(a,b,c,d,e,f,g,h,i) }
上述代码的运行结果为:
0 1 2 ha ha 100 100 7 8
-
a是值为hello的字符串,为啥unsafe.Sizeof(a)输出结果为16,同理,切片和数组需要24字节的内存
字符串类型在go里是个结构,包含指向底层数组的指针和长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节
对切片和数组来说,指针字段需要8字节,长度和容量字段分别需要8字节,共24字节
-
golang中字符串和int的互相转换
int -> string: fmt.Sprint(int)
int -> string: strconv.Itoa(int)
string -> int: strconv.Atoi(string)
-
switch关键字可以有如下两种使用方式
package main import "fmt" func main() { /* 定义局部变量 */ var grade string = "B" var marks int = 90 switch marks { case 90: grade = "A" case 80: grade = "B" case 50,60,70 : grade = "C" default: grade = "D" } switch { case grade == "A" : fmt.Printf("优秀!\n" ) case grade == "B", grade == "C" : fmt.Printf("良好\n" ) case grade == "D" : fmt.Printf("及格\n" ) case grade == "F": fmt.Printf("不及格\n" ) default: fmt.Printf("差\n" ); } fmt.Printf("你的等级是 %s\n", grade ); }
-
结构体类型方法
package main import ( "fmt" ) /* 定义结构体 */ type Circle struct { radius float64 } func main() { var c1 Circle c1.radius = 10.00 fmt.Println("Area of Circle(c1) = ", c1.getArea()) } //该 method 属于 Circle 类型对象中的方法 func (c Circle) getArea() float64 { //c.radius 即为 Circle 类型对象中的属性 return 3.14 * c.radius * c.radius }
上述代码中,getArea方法前(c Circle)表明getArea方法是Circle结构体的方法
-
go语言函数引用传值和c一样,是传递一个地址上去,这样就能在函数中同步
package main import "fmt" func main() { /* 定义局部变量 */ var a int = 100 var b int = 200 fmt.Printf("交换前,a 的值 : %d\n", a ) fmt.Printf("交换前,b 的值 : %d\n", b ) /* 调用 swap() 函数 * &a 指向 a 指针,a 变量的地址 * &b 指向 b 指针,b 变量的地址 */ swap(&a, &b) fmt.Printf("交换后,a 的值 : %d\n", a ) fmt.Printf("交换后,b 的值 : %d\n", b ) } func swap(x *int, y *int) { *x, *y = *y, *x }
-
go的指针太有意思了,可以直接访问struct中的数据
package main import "fmt" type Student struct { name string age int } func main() { var stu Student stu.name = "mickey" stu.age = 23 printName(&stu) } func printName(stu *Student) { fmt.Println(stu, *stu, stu.name) }
上述代码的输出为
&{mickey 23} {mickey 23} mickey
-
go虽然是一门编译性的语言,但是可以动态对数组(切片)进行append操作,与slice有关的api有
- make:建立一个slice
- append:数组追加
- copy:数组拷贝
- len:获取slice的长度
- cap:获取slice的最大容量
- newSlice = slice[i:j:k],len = j - i,cap = k - i,如未指定k的话,则默认k为原slice的cap
package main import "fmt" func main() { var numbers []int printSlice(numbers) /* 允许追加空切片 */ numbers = append(numbers, 0) printSlice(numbers) /* 向切片添加一个元素 */ numbers = append(numbers, 1) printSlice(numbers) /* 同时添加多个元素 */ numbers = append(numbers, 2,3,4) printSlice(numbers) /* 添加一个数组,需要使用...运算符 */ arr := []int {1, 3, 5} numbers = append(numbers, arr...) printSlice(numbers) /* 创建切片 numbers1 是之前切片的两倍容量*/ numbers1 := make([]int, len(numbers), (cap(numbers))*2) /* 拷贝 numbers 的内容到 numbers1 */ copy(numbers1,numbers) printSlice(numbers1) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }
-
在对切片进行截取操作的时候,需要注意,截取出来的slice和原slice指针指向的是同一个数组,为了杜绝错误的数据修改,可以将新slice的长度和容量设置成一样的,这样append的时候,会重新生成一个更大长度的数组,与原slice指向的数组不同
// 创建字符串切片 // 其长度和容量都是 5 个元素 source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"} // 对第三个元素做切片,并限制容量 // 其长度和容量都是 1 个元素 slice := source[2:3:3] // 向 slice 追加新字符串 slice = append(slice, "Kiwi")
常见的错误
a := []int{1, 2, 3, 4} b := append(a[:1], a[2:]...) fmt.Println(a, b) [1, 3, 4, 4] [1, 3, 4] // 这行代码的本意是复制 a 中除了下标为 1 的全部元素,却发现打印出来 a 改变了,原因就是 a[:1] 和 a 指向相同的 ptr,因此 cap > len b = append(a[:1:1], a[2:]...) fmt.Println(a, b) [1, 2, 3, 4] [1, 3, 4]
-
go声明数组和声明切片的不同,如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片,只有不指定值的时候,才会创建切片
// 创建有3个元素的整型数组 array := [3]int{10, 20, 30} // 创建长度和容量都是3的整形切片 slice := []int{10, 20, 30}
-
golang可以使用内建函数 make 也可以使用 map 关键字来定义Map,但是不初始化map,那么就会创建一个nil map,nil map不能用来存放键值对
/* 声明变量,默认 map 是 nil */ var map_variable map[key_data_type]value_data_type /* 使用 make 函数 */ map_variable := make(map[key_data_type]value_data_type)
-
go中从map中获取值并判断键是否存在
value, exists := colors["Blue"] if exists { fmt.Println(value) }
value := colors["Blue"] if value != "" { fmt.Println(value) }
-
golang的range关键字可以用于遍历string,slice和map,需要注意的是,遍历string的时候,返回的是字符的unicode形式
nums := []int {2, 3, 4} for _, num := range nums { fmt.Println(num) } kvs = map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Println(k, v) } for i, c := range "go" { fmt.Println(i, c) }
-
go中的interface
/* 定义接口 */ type interface_name interface { method_name1 [return_type] method_name2 [return_type] }
package main import "fmt" type Phone interface { call() } type IPhone struct { name string } func (p IPhone) call() { fmt.Println(p.name) } type INT int func (i INT) call() { fmt.Println(i) } func main() { var a INT = 76 var p Phone = a p.call() iphone := IPhone {name: "abc"} p = iphone p.call() }
-
go中的new操作符
new(type)
获得type类型的一个地址,多用于指针数组初始化var pointerArr = [2]*int {new(int), new(int)} *pointerArr[0] = 1
-
go的method和func的区别
method基于
struct
,只有用.运算符才能调用func就是普通函数
-
go中import包的顺序如下
- 首先编译器会查找Go的安装目录
- 按顺序查找GOPATH变量里列出的目录
-
go的import操作是import一个包,包下所有文件的大写字母开头的变量都能在包外拿到,包内的文件可以引用包内所有的变量,因此在一个包内不能定义同名的全局func和var
-
go interface的方法集
struct可以定义值接受者声明的方法和指针接受者声明的方法,需要注意的是,如果使用值接收者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口,如果使用指针接收者来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口
type notifier interface { notify() } type user struct { name string age int } func (u *user) notify() { fmt.Println(u.name, u.age) } func main() { u := user {"name", 23} sendNotity(u) // 报错 sendNotity(&u) // 成功 } func sendNotity(n notifier) { n.notify() }
-
go的struct引入另外一个struct有两种声明方式,变量声明和嵌套声明,嵌套声明的话存在提升,即可以直接在外层变量访问内部变量的参数
package main import "fmt" type student struct { id int } type person struct { student name string } type person1 struct { stu student name string } func main() { per := person{ student{ id: 21, }, "mickey", } per1 := person1{ student{ id: 21, }, "mickey", } fmt.Println(per.id) fmt.Println(per1.stu.id) }
-
golang的goroutine并发,runtime.GOMAXPROCS(1)意思是为每个调度器分配一个逻辑处理器,首先定义一个sync.WaitGroup的变量,设置需要等待的goroutine数量,在一个goroutine中执行
defer wg.Done()
,在需要阻塞执行的地方执行wg.Wait()
package main import ( "sync" "runtime" "fmt" ) func main() { runtime.GOMAXPROCS(1) // runtime.GOMAXPROCS(runtime.NumCPU()) var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() for count := 0; count < 3; count++ { for char := 'a'; char < 'a' + 26; char++ { fmt.Printf("%c 1 ", char) } } }() go func() { defer wg.Done() for count := 0; count < 3; count++ { for char := 'a'; char < 'a' + 26; char++ { fmt.Printf("%c 2 ", char) } } }() fmt.Println("Wait") wg.Wait() fmt.Println("finish") }
-
go的runtime模块提供runtime.Gosched()方法使得当前goroutine退出线程,返回调度器等待序列
-
go中goroutine可能同时修改同一个变量,需要锁住共享资源
-
原子函数(以int64举例子),LoadInt64函数用于获取一个int64变量,StoreInt64函数用于存储一个int64变量
import "sync/atomic" var shutdown int64 atomic.LoadInt64(&shutdown) atomic.StoreInt64(&shutdown, 1)
-
互斥锁(mutex)
package main import ( "fmt" "runtime" "sync" ) var ( // counter是所有goroutine都要增加其值的变量 counter int // wg用来等待程序结束 wg sync.WaitGroup // mutex 用来定义一段代码临界区 mutex sync.Mutex ) func main() { wg.Add(2) go incCounter(1) go incCounter(2) wg.Wait() } func incCounter(id int) { defer wg.Done() for count := 0; count < 2; count++ { mutex.Lock() { // 捕获counter的值 value := counter runtime.Gosched() value++ counter = value } mutex.UnLock() } }
即使goroutine执行runtime.Gosched()退出当前线程,调度器也会再次分配这个goroutine继续运行
-
-
break/continue 标识符,用于在多重循环中跳转
next: for i := .... { for j := ... { continue/break next } }
-
通道(用于在goroutine中同步数据)
-
无缓冲的通道
channel := make(chan int)
-
有缓冲的通道
tasks := make(chan string, num int)
-
通道分为可读可写通道,只读通道,只写通道,如上所示的
chan int
是可读可写通道import "time" ticker := time.NewTicker(time.Second * 10) var ch <-chan time.Time // 定义一个只读通道 ch = ticker // right var ch1 chan time.Time ch1 = ticker // wrong
-
-
golang make(chan int) 和 make(chan int, 1) 的区别
不带缓冲的channel写完就阻塞,这种情况只有其他协程中有对应的读才能解除阻塞。而带缓冲的channel要直到写满+1才阻塞
如下所示是可以的,不会报死锁
func main() { ch := make(chan int) go func() { time.Sleep(time.Second * 5) <-ch }() select { case ch <- 1: fmt.Println("asd") } }
而下面的写法会报死锁
func main() { ch := make(chan int) select { case ch <- 1: fmt.Println("asd") } go func() { time.Sleep(time.Second * 5) <-ch }() }
-
golang中提供一个vender目录用于存放第三方的golang包,我们可以使用go get命令拉取相应package到$GOPATH目录,然后cp到vender目录
-
golang中main、init方法的调用
init方法是在任何package中都可以出现,但是建议每个package中只包含一个init函数比较好,容易理解。但是main方法只能用在package main中。
Go程序会自动调用init和main,所以你不需要在任何地方调用这两个函数
-
go-import下划线的作用
在go中,import的作用是导入其他package,import 下划线(如: import _ “hello/go”)的作用:当导入一个包时,该包下的文件里所有init函数都会被执行,然而,有时候我们并不是要把整个包都导入进来,仅仅是希望它执行init函数而已,这个时候就可以使用import _ 来引用该包,需要记住的是,import _ 无法通过包名来调用包中的其他函数
-
js中有Number.MAX_SAFE_INTEGER,python有float(‘inf’)来代表最大数值,golang没有自带的最大数常量,可以用位运算来表示
无符号整型uint const UINT_MIN uint = 0 const UINT_MAX = ^uint(0) 有符号整型int 根据补码,其最大值二进制表示,首位0,其余1 const INT_MAX = int(^uint(0) >> 1) const INT_MIN = ^INT_MAX
-
go中的int类型并不是等于int32或者int64,而是随着平台位数变化的,strconv.Atoi方法得到的是int类型,如果要得到确切位数的int变量,需要使用strconv.ParseInt(s string, base int, bitSize int),在golang内部,是根据如下的代码来确定平台的int位数的,32位的系统(^uint(0)»63)就是32bit的0,64位则为前63位的0以及最后1位的1
const intSize = 32 << uint(^uint(0)>>63) const IntSize = intSize // number of bits in int, uint (32 or 64)
-
golang中的随机数rand.Intn()方法每次build运行得到的结果是一样的,除非使用rand.Seed(),而且Seed()的参数最好是变化的,例如time.Now().Unix()这样的,才能每次build运行的时候做到真正的随机
-
go中sync.Mutex和sync.RWMutex的区别
Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁.适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁也叫做全局锁
RWMutex是一个读写锁,该锁可以加多个读锁或者一个写锁,其经常用于读次数远远多于写次数的场景.
- func (rw *RWMutex) Lock() 写锁,如果在添加写锁之前已经有其他的读锁和写锁,则lock就会阻塞直到该锁可用,为确保该锁最终可用,已阻塞的 Lock 调用会从获得的锁中排除新的读取器,即写锁权限高于读锁,有写锁时优先进行写锁定
- func (rw *RWMutex) Unlock() 写锁解锁,如果没有进行写锁定,则就会引起一个运行时错误
-
func (rw *RWMutex) RLock() 读锁,当有写锁时,无法加载读锁,当只有读锁或者没有锁时,可以加载读锁,读锁可以加载多个,所以适用于"读多写少"的场景
- 读锁解锁,RUnlock 撤销单次 RLock 调用,它对于其它同时存在的读取器则没有效果
-
go中defer、return、返回值之间执行顺序的坑,主要是返回值为匿名的时候,defer无法修改返回值,除非返回的是指针,以及defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体
-
在go中,如果需要大量的字符串拼接操作,可以使用bytes的buffer来减少消耗
buffer := bytes.NewBufferString(str) for _, s := range strs { buffer.WriteString(s) } str = buffer.String()
-
golang里int转string有两种常用的方法,fmt.Sprintf(“%d”, i)以及strconv.Itoa(i), 后者的性能远好于前者
startTime := time.Now() for i := 0; i < 10000; i++ { fmt.Sprintf("%d", i) } fmt.Println(time.Now().Sub(startTime)) startTime = time.Now() for i := 0; i < 10000; i++ { strconv.Itoa(i) } fmt.Println(time.Now().Sub(startTime)) 1.214578ms 400.521µs
-
golang中的string的底层实际是byte数组,由于golang默认是utf8的,中文占三个字节,当使用len函数的时候,会发现一个汉字len为3,使用utf8.RuneCountInString()可以正确得到结果
-
golang为了方便开发者使用,将I/O操作封装到了如下几个包中
- io 为IO原语,提供了基本的IO接口
- ioutil 封装一些实用的IO函数,例如ReadFile
- fmt 实现格式化I/O,类似于c中的printf和scanf
- bufio 实现带缓存的I/O
-
golang中heap —— container/heap
type Interface interface { sort.Interface Push(x interface{}) Pop() interface{} }
堆接口继承自sort.Interface,因此需要定义sort的三个方法以及heap的两个方法
type IntHeap []int func (h IntHeap) Len() int { return len(h) } func (h IntHeap) Less(i, j int) { return h[i] < h[j] } func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) } func (h *IntHeap) Pop() interface{} { old := *h n := len(old) x := old[n - 1] *h = old[0 : n - 1] return x }
h := &IntHeap{2, 1, 5} heap.Init(h) heap.Push(h, 3) heap.Pop(h)
-
golang中ring —— container/ring
type Ring struct { next, prev *Ring Value interface{} }
环的结构有点特殊,环的尾部就是头部,所以每个元素实际上就可以代表自身的这个环。它不需要像list一样保持list和element两个结构,只需要保持一个结构就行
ring := ring.New(3) for i := 1; i <= 3; i++ { ring.Value = i ring = ring.Next() } // 计算1+2+3 s := 0 ring.Do(func(p interface{}){ s += p.(int) }) fmt.Println("sum is", s)
-
golang 常用的时间函数
- time.Unix(sec, nsec int64) 通过Unix时间戳生成time.Time实例
- time.Time.Unix() 得到Unix时间戳
- time.Time.UnixNano() 得到Unix时间戳的纳秒表示
-
time.Parse、time.ParseInLocation以及time.Time.Format 时间文本序列化/反序列化
t, _ := time.Parse("2006-01-02 15:04:05", "2016-06-13 09:14:00") fmt.Println(time.Now().Sub(t).Hours())
2016-06-13 09:14:00 这个时间可能是参数传递过来的。这段代码的结果跟预期的不一样,原因是 time.Now() 的时区是 time.Local,而 time.Parse 解析出来的时区却是 time.UTC(可以通过 Time.Location() 函数知道是哪个时区)。在中国,它们相差 8 小时,所以,一般的,我们应该总是使用 time.ParseInLocation 来解析时间,并给第三个参数传递 time.Local
至于为什么要使用2006-01-02 15:04:05 这个字符串,这是固定写法,类似于其他语言中 Y-m-d H:i:s 等,而选择这个时间点,也是出于好记的考虑,官方的例子:Mon Jan 2 15:04:05 MST 2006,另一种形式 01/02 03:04:05PM ‘06 -0700,对应是 1、2、3、4、5、6、7;常见的格式:2006-01-02 15:04:05,很好记:2006年1月2日3点4分5秒~
-
time.Time.Round(最接近)和time.Time.Truncate(向下取整)
t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2016-06-13 15:34:39", time.Local) // 整点(向下取整) fmt.Println(t.Truncate(1 * time.Hour)) // 整点(最接近) fmt.Println(t.Round(1 * time.Hour)) // 整分(向下取整) fmt.Println(t.Truncate(1 * time.Minute)) // 整分(最接近) fmt.Println(t.Round(1 * time.Minute)) t2, _ := time.ParseInLocation("2006-01-02 15:04:05", t.Format("2006-01-02 15:00:00"), time.Local) fmt.Println(t2)
-
golang中的sync.Cond
sync.Cond是用来控制某个条件下,goroutine进入等待时期,等待信号到来,然后重新启动,比如:
package main import ( "fmt" "sync" "time" ) func main() { locker := new(sync.Mutex) cond := sync.NewCond(locker) done := false cond.L.Lock() go func() { time.Sleep(2e9) done = true cond.Signal() }() if (!done) { cond.Wait() } fmt.Println("now done is ", done); }
sync.Cond初始化的时候,传入的一定是Locker的指针,否则在c.L.Lock()和c.L.Unlock()的时候会频繁复制锁,导致失效甚至死锁
这里当主goroutine进入cond.Wait的时候,就会进入等待,当从goroutine发出信号之后,主goroutine才会继续往下面走
sync.Cond还有一个BroadCast方法,用来通知唤醒所有等待的goroutine
package main import ( "fmt" "sync" "time" ) var locker = new(sync.Mutex) var cond = sync.NewCond(locker) func test(x int) { cond.L.Lock() // 获取锁 cond.Wait() // 等待通知 暂时阻塞 fmt.Println(x) time.Sleep(time.Second * 1) cond.L.Unlock() // 释放锁,不释放的话将只会有一次输出 } func main() { for i := 0; i < 40; i++ { go test(i) } fmt.Println("start all") cond.Broadcast() // 下发广播给所有等待的goroutine time.Sleep(time.Second * 60) }
主gouroutine开启后,可以创建多个从gouroutine,从gouroutine获取锁后,进入cond.Wait状态,当主gouroutine执行完任务后,通过BroadCast广播信号。 处于cond.Wait状态的所有gouroutine收到信号后将全部被唤醒并往下执行。需要注意的是,从gouroutine执行完任务后,需要通过cond.L.Unlock释放锁, 否则其它被唤醒的gouroutine将没法继续执行。
-
channel可以实现大多数sync.Cond的功能(close -> broadcase,insert一个数值 -> signal),但是channel不能reopen wait
package main import ( "fmt" "sync" "time" ) var count int = 4 func main() { ch := make(chan struct{}, 5) // 新建 cond var l sync.Mutex cond := sync.NewCond(&l) for i := 0; i < 5; i++ { go func(i int) { // 争抢互斥锁的锁定 cond.L.Lock() defer func() { cond.L.Unlock() ch <- struct{}{} }() // 条件是否达成 for count > i { cond.Wait() fmt.Printf("收到一个通知 goroutine%d\n", i) } fmt.Printf("goroutine%d 执行结束\n", i) }(i) } // 确保所有 goroutine 启动完成 time.Sleep(time.Millisecond * 20) // 锁定一下 fmt.Println("broadcast...") cond.L.Lock() count -= 1 cond.Broadcast() cond.L.Unlock() time.Sleep(time.Second) fmt.Println("signal...") cond.L.Lock() count -= 2 cond.Signal() cond.L.Unlock() time.Sleep(time.Second) fmt.Println("broadcast...") cond.L.Lock() count -= 1 cond.Broadcast() cond.L.Unlock() for i := 0; i < 5; i++ { <-ch } }
github上关于remove sync.Cond的issue proposal: Go 2: sync: remove the Cond type
-
临时连接池 sync.Pool
有时候在go中,不同的goroutine需要同时创建一些对象,而对象又都是占用内存的,进而导致的就是内存回收的GC压力陡增,造成”并发大-内存占用大-GC缓慢-处理并发能力降低-并发更大这样的恶性循环”,在这个时候,我们非常迫切需要有一个对象池,每个goroutine不再自己单独创建对象,而是从对象池中获取出一个对象(如果池中已经有的话),这就是sync.Pool出现的目的了
// keyBufPool returns []byte buffers for use by PickServer's call to // crc32.ChecksumIEEE to avoid allocations. (but doesn't avoid the // copies, which at least are bounded in size and small) var keyBufPool = sync.Pool{ New: func() interface{} { b := make([]byte, 256) return &b }, } func (ss *ServerList) PickServer(key string) (net.Addr, error) { ss.mu.RLock() defer ss.mu.RUnlock() if len(ss.addrs) == 0 { return nil, ErrNoServers } if len(ss.addrs) == 1 { return ss.addrs[0], nil } bufp := keyBufPool.Get().(*[]byte) n := copy(*bufp, key) cs := crc32.ChecksumIEEE((*bufp)[:n]) keyBufPool.Put(bufp) return ss.addrs[cs%uint32(len(ss.addrs))], nil }
这是实际项目中的一个例子,这里使用keyBufPool的目的是为了让crc32.ChecksumIEEE所使用的[]bytes数组可以重复使用,减少GC的压力。
但是这里可能会有一个问题,我们没有看到Pool的手动回收函数。 那么是不是就意味着,如果我们的并发量不断增加,这个Pool的体积会不断变大,或者一直维持在很大的范围内呢?
答案是不会的,sync.Pool的回收是有的,它是在系统自动GC的时候,触发pool.go中的poolCleanup函数
-
golang基础库 flag 的用法
flag可以用来解析命令行的参数,多用于使用flagSet初始化config配置
func main() { flagSet := flag.NewFlagSet("flagSet", flag.ExitOnError) flagSet.String("name", "mickey0524", "input the name") flagSet.Int("age", 23, "input the age") flagSet.Bool("isBoy", true, "input the sex") flagSet.parse(os.Args[1:]) fmt.Println(flagSet.Lookup("name").Value.String()) fmt.Println(flagSet.Lookup("age").Value.(flag.Getter).Get().Int) }
go run main.go mic 23 mic 23
-
go-svc框架
在阅读NSQ源码的时候,发现nsqlookupd模块使用开源框架svc来开启进程以及控制进程的退出,在这记录一下
svc使用起来非常简单,定义一个struct实现svc的service接口即可
简单使用
package main import ( "fmt" "syscall" "time" "github.com/judwhite/go-svc/svc" ) type program struct{} func main() { p := &program{} if err := svc.Run(p, syscall.SIGINT, syscall.SIGTERM); err != nil { fmt.Println("svc err") } } func (p *program) Init(env svc.Environment) error { fmt.Println("init", env) return nil } func (p *program) Start() error { fmt.Println("start") go func() { time.Sleep(time.Second * 1) }() return nil } func (p *program) Stop() error { fmt.Println("stop") return nil }
注意事项
- Start方法中不能只直接阻塞,需要在Start方法中新开goroutine去写需要阻塞的代码
- svc.Run()方法的第二个参数可以指定需要程序监听的信号,默认情况下不指定的话,默认会监听 SIGINT和 SIGTERM两个
实现原理
// Run runs your Service. // // Run will block until one of the signals specified in sig is received. // If sig is empty syscall.SIGINT and syscall.SIGTERM are used by default. func Run(service Service, sig ...os.Signal) error { env := environment{} if err := service.Init(env); err != nil { return err } if err := service.Start(); err != nil { return err } if len(sig) == 0 { sig = []os.Signal{syscall.SIGINT, syscall.SIGTERM} } signalChan := make(chan os.Signal, 1) signalNotify(signalChan, sig...) //signalNotify方法其实就是 signal.Notify 方法 //var signalNotify = signal.Notify <-signalChan return service.Stop() }
-
golang atomic包的Value类型
atomic.Value是goroutine间共享的变量,Load()方法取最后一次被Store()写入的数据
package main import ( "fmt" "sync" "sync/atomic" "time" ) type myMap map[int]int var mutex sync.Mutex func main() { var m atomic.Value m.Store(make(myMap)) for i := 0; i < 10; i++ { go func(j int) { mutex.Lock() defer mutex.Unlock() m1 := m.Load().(myMap) m2 := make(myMap) for k, v := range m1 { m2[k] = v } m2[j] = j m.Store(m2) time.Sleep(time.Second * 2) }(i) } for { m1 := m.Load().(myMap) fmt.Println(m1) time.Sleep(time.Second * 1) } }