首页
友情链接
统计
推荐
GitHub
Search
1
蚁群算法学习
474 阅读
2
golang 锁
359 阅读
3
Go 栈和堆
348 阅读
4
GO语言基础plus
336 阅读
5
golang Map
275 阅读
GO
源码阅读
基础
Python
登录
Search
标签搜索
慕课笔记
BeforeIeave
累计撰写
9
篇文章
累计收到
2
条评论
首页
栏目
GO
源码阅读
基础
Python
页面
友情链接
统计
推荐
GitHub
搜索到
8
篇与
的结果
2022-07-27
goalng Slice&String
1 字符串1.1 字符串的源码//runtime 包中的定义 type stringStruct struct { str unsafe.Pointer len int } //reflact 包种的string头部 type StringHeader struct{ Data uintptr Len int //表示的是字节数组的长度,不是字节的数量(遍历中文的时候会出问题) }说明:字符串本质上是一个结构体,str指针指向的是底层的Byte数组。 1.2 字符串的遍历方法一:直接用len(str)作为限制条件,使用for进行访问。最后记得转换为string方法二:直接使用for range的方式进行访问,最后转换为string或用fmt.Printf("%c", each)打印。注意:使用range的时候,会自动解码成rune的形式,由utf8.go文件进行解码。1.3 字符串的切分先将其转为rune数组,再切片,最后转为strings = string([]rune(s)[:3])1.4 注意事项字符串的底层是Byte数组,存在多个字符串的底层共用一个字节内存,所以字符串数不允许更改的。2 切片2.1 切片源码type slice struct { array unsafe.Pointer //指向底层数组的指针 len int //切片引用的长度 cap int //整个切片的大小(容量) }2.2 切片的追加1.不需要扩容的时候:当向切片追加元素时,仅由编译器调整len的大小。2.需要扩容的时候:编译时转为调用runtime.growslice()函数2.3 切片的扩容机制一般情况下,会重新开辟一个两倍长的数组替代原有的数组(因为数组空间必须时连续的),但是如果容量大于目前容量的两倍,则会直接扩容成期望容量的大小。对于过大的切片,会有新的规则,若切片的长度小于1024,则容量会翻倍增长,大于1024则每次仅增加25%的大小。注意切片扩容的时候并发不安全,一定要加锁。2.4 扩容源码阅读func growslice(et *_type, old slice, cap int) slice { if raceenabled { callerpc := getcallerpc() racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, abi.FuncPCABIInternal(growslice)) } if msanenabled { msanread(old.array, uintptr(old.len*int(et.size))) } if asanenabled { asanread(old.array, uintptr(old.len*int(et.size))) } if cap < old.cap { panic(errorString("growslice: cap out of range")) } if et.size == 0 { // append should not create a slice with nil pointer but non-zero len. // We assume that append doesn't need to preserve old.array in this case. return slice{unsafe.Pointer(&zerobase), old.len, cap} } newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { const threshold = 256 if old.cap < threshold { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { // Transition from growing 2x for small slices // to growing 1.25x for large slices. This formula // gives a smooth-ish transition between the two. newcap += (newcap + 3*threshold) / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } } var overflow bool var lenmem, newlenmem, capmem uintptr // Specialize for common values of et.size. // For 1 we don't need any division/multiplication. // For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant. // For powers of 2, use a variable shift. switch { case et.size == 1: lenmem = uintptr(old.len) newlenmem = uintptr(cap) capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem) case et.size == goarch.PtrSize: lenmem = uintptr(old.len) * goarch.PtrSize newlenmem = uintptr(cap) * goarch.PtrSize capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize) case isPowerOfTwo(et.size): var shift uintptr if goarch.PtrSize == 8 { // Mask shift for better code generation. shift = uintptr(sys.Ctz64(uint64(et.size))) & 63 } else { shift = uintptr(sys.Ctz32(uint32(et.size))) & 31 } lenmem = uintptr(old.len) << shift newlenmem = uintptr(cap) << shift capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift) default: lenmem = uintptr(old.len) * et.size newlenmem = uintptr(cap) * et.size capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) capmem = roundupsize(capmem) newcap = int(capmem / et.size) } // The check of overflow in addition to capmem > maxAlloc is needed // to prevent an overflow which can be used to trigger a segfault // on 32bit architectures with this example program: // // type T [1<<27 + 1]int64 // // var d T // var s []T // // func main() { // s = append(s, d, d, d, d) // print(len(s), "\n") // } if overflow || capmem > maxAlloc { panic(errorString("growslice: cap out of range")) } var p unsafe.Pointer if et.ptrdata == 0 p = mallocgc(capmem, nil, false) // The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length). // Only clear the part that will not be overwritten. memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) } else { // Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory. p = mallocgc(capmem, et, true) if lenmem > 0 && writeBarrier.enabled { // Only shade the pointers in old.array since we know the destination slice p // only contains nil pointers because it has been cleared during alloc. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata) } } memmove(p, old.array, lenmem) return slice{p, old.len, newcap} }
2022年07月27日
178 阅读
0 评论
0 点赞
2022-07-27
golanfg Channel
1 内存于通信不要通过共享内存的方式通信,而是应该通过通信的方式共享内存。例如不要使用传输变量地址的方式检测变量的值,而可以用管道直接判断,因为管道有内容才会允许被取值。1.1 优势1.可以避免协程竞争和数据冲突的问题。2.管道示更高级的抽象,可以降低开发难度,增加程序的可读性。3.模块之间更易解耦,增强可扩展性和可维护性。2 channeltype hchan struct { //缓存区的构成-5行 qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 //状态值 0-开启 1-关闭 elemtype *_type // element type //指向链表的当前执行的位置 sendx uint // send index recvx uint // receive index //发送和接收的链表 recvq waitq // list of recv waiters sendq waitq // list of send waiters //保护此结构体的所有字段--任何操作结构体的函数都要加锁-目的是保护结构体本身 lock mutex 2.1 channel缓存区的构建2.2 底层原理2.2.1 语法糖<-关键字其实是一个语法糖,编译的时候会变为runtime.chansend1()。// entry point for c <- x from compiled code //go:nosplit func chansend1(c *hchan, elem unsafe.Pointer) { chansend(c, elem, true, getcallerpc()) }->也是语法糖,编译阶段i<- c 转化为runtime.chanrecv1(); i, ok <- c转化为runtime.chanrecv2()。最终还是调用chanrecv()方法。// entry points for <- c from compiled code //go:nosplit func chanrecv1(c *hchan, elem unsafe.Pointer) { chanrecv(c, elem, true) } //go:nosplit func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) { _, received = chanrecv(c, elem, true) return }2.2.2 channel发送的情形直接发送指在数据发送之前,已经有协程G在等待接收了。则先唤醒协程,再将数据直接拷贝。func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { if c == nil { if !block { return false } gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2) throw("unreachable") } //是否为空的判断 if debugChan { print("chansend: chan=", c, "\n") } if raceenabled { racereadpc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(chansend)) } if !block && c.closed == 0 && full(c) { return false } var t0 int64 if blockprofilerate > 0 { t0 = cputicks() } //上锁 lock(&c.lock) //判断channel是否被关闭 if c.closed != 0 { unlock(&c.lock) panic(plainError("send on closed channel")) } //从等待队列中取协程 if sg := c.recvq.dequeue(); sg != nil { // Found a waiting receiver. We pass the value we want to send // directly to the receiver, bypassing the channel buffer (if any). send(c, sg, ep, func() { unlock(&c.lock) }, 3) return true } //若等待队列无法取出协程则存入缓存 //缓存 //... chansend() 调用了 send()func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) { if raceenabled { if c.dataqsiz == 0 { racesync(c, sg) } else { racenotify(c, c.recvx, nil) racenotify(c, c.recvx, sg) c.recvx++ if c.recvx == c.dataqsiz { c.recvx = 0 } c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz } } if sg.elem != nil { sendDirect(c.elemtype, sg, ep) //发送数据 sg.elem = nil } gp := sg.g unlockf() gp.param = unsafe.Pointer(sg) sg.success = true if sg.releasetime != 0 { sg.releasetime = cputicks() } //与gopark()相反,作用是唤醒协程 goready(gp, skip+1) }send() 调用 sendDirect()func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) { //sudog结构体的elem指向的是传入参数的地址 dst := sg.elem typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size) memmove(dst, src, t.size) //内存移动-直接拷贝 }放入缓存没有协程G接收数据,但是有数据且有缓存空间。chansend()的剩余代码:func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { //... //之上为直接发送 //缓存区已缓存的数量 < 总量 if c.qcount < c.dataqsiz { // Space is available in the channel buffer. Enqueue the element to send. qp := chanbuf(c, c.sendx) if raceenabled { racenotify(c, c.sendx, nil) } //参数移动到缓存单元 typedmemmove(c.elemtype, qp, ep) //维护数据 c.sendx++ if c.sendx == c.dataqsiz { c.sendx = 0 } c.qcount++ unlock(&c.lock) return true } //缓存区已满 //... }休眠等待func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { //... //缓存已满 if !block { unlock(&c.lock) return false } // Block on the channel. Some receiver will complete our operation for us. gp := getg() //拿到自己的协程结构体 mysg := acquireSudog() //包装自己 mysg.releasetime = 0 if t0 != 0 { mysg.releasetime = -1 } // No stack splits between assigning elem and enqueuing mysg // on gp.waiting where copystack can find it. mysg.elem = ep mysg.waitlink = nil mysg.g = gp mysg.isSelect = false mysg.c = c gp.waiting = mysg gp.param = nil //将自己包装成sudog结构体--协程 //将自己放入等待队列 c.sendq.enqueue(mysg) atomic.Store8(&gp.parkingOnChan, 1) //进入休眠 gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2) KeepAlive(ep) //被唤醒之后只需维护数据-以下的代码,不用接收数据---已经被取走了 // someone woke us up. if mysg != gp.waiting { throw("G waiting list is corrupted") } gp.waiting = nil gp.activeStackChans = false closed := !mysg.success gp.param = nil if mysg.releasetime > 0 { blockevent(mysg.releasetime-t0, 2) } mysg.c = nil releaseSudog(mysg) if closed { if c.closed == 0 { throw("chansend: spurious wakeup") } panic(plainError("send on closed channel")) } return true }2.2.3 channel的数据接收有等待的协程,从协程接收数据即数据接收前已经有协程等待发送,且没有缓存时:直接拷贝数据然后唤醒send的协程。func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) { //block指是否阻塞 if debugChan { print("chanrecv: chan=", c, "\n") } if c == nil { if !block { return } gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2) throw("unreachable") } // Fast path: check for failed non-blocking operation without acquiring the lock. if !block && empty(c) { // After observing that the channel is not ready for receiving, we observe whether the // channel is closed. // // Reordering of these checks could lead to incorrect behavior when racing with a close. // For example, if the channel was open and not empty, was closed, and then drained, // reordered reads could incorrectly indicate "open and empty". To prevent reordering, // we use atomic loads for both checks, and rely on emptying and closing to happen in // separate critical sections under the same lock. This assumption fails when closing // an unbuffered channel with a blocked send, but that is an error condition anyway. if atomic.Load(&c.closed) == 0 { // Because a channel cannot be reopened, the later observation of the channel // being not closed implies that it was also not closed at the moment of the // first observation. We behave as if we observed the channel at that moment // and report that the receive cannot proceed. return } // The channel is irreversibly closed. Re-check whether the channel has any pending data // to receive, which could have arrived between the empty and closed checks above. // Sequential consistency is also required here, when racing with such a send. if empty(c) { // The channel is irreversibly closed and empty. if raceenabled { raceacquire(c.raceaddr()) } if ep != nil { typedmemclr(c.elemtype, ep) } return true, false } } var t0 int64 if blockprofilerate > 0 { t0 = cputicks() } lock(&c.lock) //是否已经被关闭 if c.closed != 0 && c.qcount == 0 { if raceenabled { raceacquire(c.raceaddr()) } unlock(&c.lock) if ep != nil { typedmemclr(c.elemtype, ep) } return true, false } //再发送队列中拿到协程 if sg := c.sendq.dequeue(); sg != nil { recv(c, sg, ep, func() { unlock(&c.lock) }, 3) return true, true }chanrecv() 调用 recv() 函数func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) { //判断缓存区是否为空 if c.dataqsiz == 0 { if raceenabled { racesync(c, sg) } if ep != nil { recvDirect(c.elemtype, sg, ep) //缓存区为空则直接接收--调用内存移动函数 } } else { qp := chanbuf(c, c.recvx) if raceenabled { racenotify(c, c.recvx, nil) racenotify(c, c.recvx, sg) } // copy data from queue to receiver if ep != nil { typedmemmove(c.elemtype, ep, qp) } // copy data from sender to queue typedmemmove(c.elemtype, qp, sg.elem) c.recvx++ if c.recvx == c.dataqsiz { c.recvx = 0 } c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz } //维护协程状态 sg.elem = nil gp := sg.g unlockf() gp.param = unsafe.Pointer(sg) sg.success = true if sg.releasetime != 0 { sg.releasetime = cputicks() } goready(gp, skip+1) //唤醒发送协程--ta不用再发送了 }有等待的协程,从缓存接收channel有缓存,自己将缓存中的数据拿走,再将发送队列等待的协程的数据放入缓存中,最后再唤醒发送的协程。 recv() 之后的部分:func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) { if c.dataqsiz == 0 { if raceenabled { racesync(c, sg) } if ep != nil { recvDirect(c.elemtype, sg, ep) //缓存区为空则直接接收--调用内存移动函数 } } else { //直接取值的情形,else-有缓存 qp := chanbuf(c, c.recvx) if raceenabled { racenotify(c, c.recvx, nil) racenotify(c, c.recvx, sg) } // copy data from queue to receiver if ep != nil { typedmemmove(c.elemtype, ep, qp) //直接取出缓存的数据 } // copy data from sender to queue typedmemmove(c.elemtype, qp, sg.elem) //将等待发送数据协程的数据移至缓存空间 c.recvx++ if c.recvx == c.dataqsiz { c.recvx = 0 } c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz } //维护协程状态 sg.elem = nil gp := sg.g unlockf() gp.param = unsafe.Pointer(sg) sg.success = true if sg.releasetime != 0 { sg.releasetime = cputicks() } goready(gp, skip+1) //唤醒发送协程--ta不用再发送了 }没有等待携程,接收缓存没有协程再发送队列等待,且channel有缓存。’func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) { //.. //队列中没有协程,缓存中有 if c.qcount > 0 { // Receive directly from queue qp := chanbuf(c, c.recvx) if raceenabled { racenotify(c, c.recvx, nil) } if ep != nil { typedmemmove(c.elemtype, ep, qp) //将数据移动到接收地址 } typedmemclr(c.elemtype, qp) c.recvx++ if c.recvx == c.dataqsiz { c.recvx = 0 } c.qcount-- unlock(&c.lock) return true, true }没有等待协程,阻塞接收没有任何数据存在,自己进入接收队列等待。func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) { //.. //前面没有任何可取的数据 if !block { unlock(&c.lock) return false, false } // no sender available: block on this channel. gp := getg() mysg := acquireSudog() mysg.releasetime = 0 if t0 != 0 { mysg.releasetime = -1 } // No stack splits between assigning elem and enqueuing mysg // on gp.waiting where copystack can find it. mysg.elem = ep mysg.waitlink = nil gp.waiting = mysg mysg.g = gp mysg.isSelect = false mysg.c = c gp.param = nil c.recvq.enqueue(mysg) //包装完成后将自己放入接收队列中 // Signal to anyone trying to shrink our stack that we're about // to park on a channel. The window between when this G's status // changes and when we set gp.activeStackChans is not safe for // stack shrinking. atomic.Store8(&gp.parkingOnChan, 1) //休眠 gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2) // someone woke us up--唤醒后维护一下数据,结束即可--数据已经被取走了。 if mysg != gp.waiting { throw("G waiting list is corrupted") } gp.waiting = nil gp.activeStackChans = false if mysg.releasetime > 0 { blockevent(mysg.releasetime-t0, 2) } success := mysg.success gp.param = nil mysg.c = nil releaseSudog(mysg) return true, success }2.2.4 非阻塞的channel的使用-select1.编译时判断存在接收、发送、默认路径,2.首先查看是否有可以立即执行的case,3.没有则直接进入default语句,4.没有default语句则将自己注册再所有的case的channel中,进入休眠。2.2.5 timer计时器func main(){ t := time.NewTimer(time.Second) //生成定时器,t在一秒后向其管道中塞一个值 <- t.C //从t中的通道取出数据 //要等待1s才会有值!! //... }
2022年07月27日
227 阅读
0 评论
0 点赞
2022-07-27
GO语言基础plus
1 数组、字符串、切片1.1 数组1.1.1 基础性质1.数组是一种值类型,元素的值可以被修改。2.数组的传参和赋值中,都以整体复制的方式处理。3.数组的长度是数组类型的一部分,所以不同长度的数组是不同类型。4.一个数组变量即表示整个数组,并不是隐式的指向第一个元素的指针,而是一个完整的值,如果数组过大的话,数组的赋值会有较大的开销,一般传递一个指向数组的指针,但数组指针不是数组。5.数组的内置函数len()和cap()返回值都是一样的,始终对应数组类型的长度。6.对于数组的遍历,建议使用for range,它省去了对下标越界的判断。7.数组可以有很多类型,如数值数组、字符串数组、结构体数组、函数数组、接口数组、通道数组等。8.长度为0的数组(空数组)在内存中并不占用空间,一般常用于强调某种特有类型的操作时避免分配额外的内存空间。(一般更倾向于用空结构体代替空数组)//使用空数组 c1 := make(chan [0]int) go func () { fmt.PrintLn("c1") c1<- [0]int{} } () <-c1 //使用空结构体 c2 := make(chan struct{}) go func () { fmt.PrintLn("c2") c1<- struct{}{} //struct{}表示的是类型,{}表示对应的结构体值 } () <-c11.1.2 数组的定义方式var a [3]int //数组的内容全部为0 var b = [...]int{1, 2, 3} //数组元素为1,2,3 var c = [...]int{2: 3, 1: 2} //数组元素为0,2,3 var d = [...]int{1, 2, 4: 5, 6} //数组元素为1,2,0,0,5,61.2 字符串1.2.1 字符串的基本性质1.Go语言字符串的底层数据对应的是字节数组,其中字符串的只读属性禁止了程序中对底层字节数组的元素的修改。2.字符串的赋值只是复制了数据的对应地址和长度,而不会导致底层数据的复制。3.字符串支持切片操作,不同位置的切片底层访问的是同一块内存数据。4.字符串的强转为[]byte类型的方法一般不会产生运行时的开销。5.rune[]类型其实[]int32类型,而由于底层内存结构的差异,[]rune到字符串的转换会导致重新构造字符串。1.2.2 字符串的底层结构//in the reflect.StringHeader pakage type StringHeader struct{ Data uintptr //存储字符串指向的底层字节数组 Len int //字符串的字节的长度 }1.3 切片1.3.1 切片的基本性质1.相较于字符串,它解除了只读限制,每个切片有独立的长度和容量信息。2.赋值和传参时,是将切片头信息部分按传值方式处理(因为切片头包含底层数据的指针,所以它赋值也不会导致底层数据的复制)。3.只有当切片底层数据指针为空时,切片本身才为nil,这个时候len和cap无效(如果其底层的数据指针为空,而len和cap均有值,则说明数据本身被损坏了)1.3.2 切片的定义方式var( a []int //nil切片,和nil相等,一般表示一个不存在的切片 b = []int{} //空切片,和nil不相等,一般表示一个空集合 c = []int{1, 2, 3} //含有3个元素,len 和 cap 都是3 d = c[:2] //有两个元素的切片,len 为2, cap 为3 e = c[0:2:cap(c)] //含有两个元素的切片,len = 2,cap = 3 f = c[:0] //含有0个元素的切片,len = 0,cap = 3 g = make([]int, 3) //含有3个元素的切片,len = cap = 3 h = make([]int, 2, 3) //含有两个元素的切片,len = 2,cap = 3 i = make([]int, 0, 3) //含有两个元素的切片,len = 0,cap = 3 )1.3.3 切片的底层结构type SliceHeader struct{ Data uintptr Len int Cap int //表示其指向内存空间的最大容量(对应元素的个数,非字节数) }1.3.4 基本操作1.内置的泛型函数append()可以在切片尾追加N个元素。但是注意,在容量不足的情况下,此操作会重新分配内存,可能导致巨大的开销。(在开头添加元素一般都会导致内存的重新分配,会将已有的所有数组全部赋值一次)2.copy()函数与append()函数结合可以节省创建临时切片的时间和空间。3.要实现删除切片的操作可以灵活的使用append()和copy()两个函数结合切片的截取方法。4.在判断一个切片是否为空的时候,一般不直接和nil做比较,而是获取它的长度判断其值是否为0来判断的。1.3.5 避免内存泄漏切片操作并不会复制底层的数据,而底层的数组会被保存到内存中直到它不再被引用,此种情况很可能因为内存引用导致内存泄漏,情况如下:func FindPhoneNumber (fileName string) []byte { b ,_ := ioutil.ReadFile(filName)\ return regexp.MustCompile("[0-9]+").Find(b) //返回了内存地址的引用 }而解决方法非常简单,将需要的内容复制一遍再返回即可:func FindPhoneNumber (fileName string) []byte { b ,_ := ioutil.ReadFile(filName)\ b = regexp.MustCompile("[0-9]+").Find(b) return append([]byte{}, b...) }对于指针有相同的改良方式:var a []*int{...} a = a[:len(a) - 1] //此时被删除的最后一个元素依然被引用 //此时将被删除的元素指针设置为nil即可 var a []*int{...} a[len(a) - 1] = nil a = a[:len(a) - 1]1.3.6 切片类型的强制转换例如将[]float64转换为[]int进行比较:import "sort" var a = []float64{2, 4, 5, 7, 2, 1, 88, 1} func SortFloat64FastV1(a []float64) { //类型的强制转换 var b []int = ((*[1 << 20]int)(unsafe.Pointer(&a[0])))[:len(a):cap(a)] //以int的方式给float排序 sort.Ints(b) } func SortFloatFastV2 (a []float64) { //通过reflact.SliceHeader 更行切片头的信息实现转换 var c []int aHdr := (*reflect.SliceHeader)(unsafe.Pointer(&a)) cHdr := (*reflect.SliceHeader)(unsafe.Pointer(&c)) *cHdr = *aHdr sort.Ints(c) }第一种方法是通过开辟一个很大的int数组,然后通过指针重新对数组进行操作。第二种方法是更新头部信息转换数组的类型(注意:前提要保证[]float64中没有NaN和Inf等非规范浮点数)。2 函数、方法、接口2.1 函数2.1.1 可变参数当可变参数是一个空接口类型的时候,是否解包可变参数会导致不同的结果func main(){ var a = []interface{}{123, "abc"} print(a...) //123 abc print(a) //[123 abc] } func print(a...interface{}) { fmt.Ptintln(a...) }第一个print传入的参数是a...等价于(解包)调用print(123, abc),而第二个是调用未解包的参数a,等价于直接调用print([]interface{}{123, "abc"})。2.1.2 (延迟调用defer)闭包一般匿名函数捕获了外部函数的局部变量,则我们将此种函数称为闭包(闭包对捕获的外部变量并不是以传值方式访问的,而是以引用方式!)但是这种引用方式访问外部变量的行为会导致一些隐含问题:(在for循环内使用defer语句,差评)func main() { for i:= 0; i < 3; i++ { defer func(){fmt.Print(i)}() } } // output 333原因:defer每次引用的都是同一个i,最后i的值变为3,则运行的defer函数均输出3。修改的方法:每一次传入独有的变量(地址不一样)即可://方法1 func main() { for i:= 0; i < 3; i++ { x := i //生成新的变量 defer func(){fmt.Print(x)}() } } //方法2 func main() { for i:= 0; i < 3; i++ { defer func(i int){fmt.Print(i)}(i) //以参数的形式传递 } }2.1.3 指针传递注意到:当使用切片为参数调用函数的时候,函数内部是可以更改切片的值的,这会给我们一种传递指针的假象。本质是:切片中的底层是通过隐式指针传递的,而两个指针指向的是同一块内存区域。2.1.4 补充1.函数是支持递归调用的(并且不会栈溢出的错误)。2.Go语言故意淡化了堆和栈的概念,所以我们无法分别数据是存储在哪个地方的,但是,当函数返回某个变量的地址时,编译器会将它储存在合适的地方。2.2 方法2.2.1 基础知识我们通常给某一个自己定义的类型定义相应的方法(方法绑定到对象上),注意类型的定义和方法必须要在同一个包中。type Cache struct{ m map[string]string sync.Mutex } func (p *Cache) Lookup (key string) string { //Cache类型对象的专属方法 p.Lock defer p.Unlock return p.m[key] } 2.3 接口2.3.1 接口类型的转换1.鸭子类型:指某一物种叫起来像鸭子,走起来像鸭子 那我们就认为它是鸭子。Go语言对基础类型的类型一致性要求非常严格,但对于接口的类型转换非常灵活。var( a io.ReadCloser = (*os.File)(f) //隐式转换,*os.File满足io.ReaderCloser接口 b io.Reader = a //隐式转换,io.ReaderCloser满足io.reader接口 c io.Closer = a //隐式转换,io.ReaderCloser满足io.Closer接口 d io.Reader = c.(io.reader) //显示转转,io.Closer不满足io.Reader接口 )但是有的时候,接口的转换过于灵活,可能产生无意间的适配,这需要人为限制。通常的做法是定义一个特殊的方法来区分接口,但是这只是“君子协定”,而更严格的做法是给接口定义一个私有方法(此时只有这个类被定义的包中能访问这个接口)但是这个防护也不是绝对安全。2.Go语言的接口类型是延迟绑定(编译时私有方法是否真的存在并不重要),可以实现类似虚拟函数的多态功能(调用私有类的相关方法)。常见的用法:内嵌一个(私有)类type ColoredPoint struct { Point //内置的(私有)类 Color color.RGBA }3 面向并发的内存模型Go语言将CSP模型的并发编程内置到了语言中,与Erlang不同的是Go语言的Gotoutine是共享内存的。3.1 Goroutine和系统线程1.Goroutine会以一个很小的栈空间启动,并且当栈空间不足的时候,会根据需要动态地伸缩栈的大小。2.Goroutine采用的是半抢占式的协作调度,只有当当前的Goroutine发生阻塞时才会导致调度。3.2 原子操作原子操作就是并发编程中“最小且不可并行化”的操作,可以保证共享资源的完整性。在一般情况下,原子操作都是通过“互斥”访问来保证的。但是互斥锁的方法麻烦而且效率低下,常用标准库sync/atomic的方法操作(同时,atomic.Value原子对象提供了Load()和Store()两种原子方法,用于加载和保存数据,返回任意类型的参数):import ( "sync" "sync/atomic" ) var total uint64 func worker(wg * sync.WaitGroup){ defer wg.Done() var i uint64 for i:= 0; i <= 100; i++ { atomic.AddUint64(&total, i) //保证了total的相关操作时原子性质的 } } func main(){ var wg *sync.WaitGroup wg.Add(2) go worker(&wg) go worker(&wg) wg.Wait() }当然,在性能敏感的地方可以增加一个数字型标志位,通过原子检测标志位的状态降低会互斥锁的使用频率来提高性能。3.4 顺序一致性内存问题为了最大化并行,Go语言编译器和处理器在不影响Goroutine内部顺序的前提下可能会对执行语句进行重新排序(CPU也会对一些指令乱序执行)。一般通过同步原语来给两件事进行明确的排序(同步通道),或者使用sync.Mutex互斥量的Lock()和Unlock()也可以实现。4 常见的并发模式Go语言提倡的是使用通道(channel)保证并发的顺序,使用带缓存的通道控制并发数。4.1 素数筛//GeneratNatural 生成自然数,并返回通道 func GenerateNatural() chan int { ch := make(chan int) go func(){ for i:= 2;; i++{ ch<-i } }() return ch } //通道过滤器,删除能被素数整除的数 func PrimeFilter(in <-chan int, prime int) chan int { out := make(chan int) go func(){ for{ if i := <-in; i%prime != 0 { out<-i } } }() return out } func main(){ ch := GenerateNatural() for i := 0; i < 100; i++{ prime := <-ch fmt.Printf("%v: %v\n", i+1, prime) ch = PrimeFilter(ch, prime) //利用上一个数排除能把ta整除的数,类似于迭代 } }由于每个并发处理的事情太细微,程序的性能并不理想。4.2 并发的安全退出通常需要使用select关键字,即当select有多个分支的时候, 会选择一个随机的可用的通道分支, 若没有,则选择default分支, 否则会一直保持阻塞的状态。select { case v := <- in: fmt.Println(v) //获取到了值 case <- time.After(time.Second): //超时 return }注意,通道的发送操作和接受操作是一一对应的,如果要停止多个Goroutine,那么需要创建同样数量发=的通道,代价太大了。而我们可以close一个通道来实现广播效果,从而所有从此通道的接受操作均会收到一个零值和一个可选失败标志。但是Goroutine退出的时候会进行一定的清理工作,需要增加原子操作使main函数管控所有的线程:func worker (wg *sync.WaitGroup, cannel chan bool){ defer wg.Done() for{ select{ default: fmt.Println("hello") case <-cannel: return } } } func main(){ cancel := make(chan bool) var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go worker(&wg, cancel) } time.Sleep(time.Second) close(cancel) wg.Wait() }4.3 contex包contex包主要用于简化对于处理单个请求的多个goroutine之间的请求域的数据、超时和退出等操作。(更多操作,见官方文档)func worker (wg *sync.WaitGroup, ctx contex.Context) error { defer wg.Done() for{ select{ default: fmt.Println("hello") case <-ctx.Done(): return ctx.Err() } } } func main(){ ctrx, cancel := contex.WithTimeout(contex.Background(), 10*time.Second) var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go worker(&wg, ctx) } time.Sleep(time.Second) cancel() wg.Wait() }5 错误和异常5.1 错误处理策略在需要进行关闭操作的时候,建议使用defer操作确保在任何特殊情况退出时能够保证关闭操作的执行,同时确保了代码的整洁性。5.2 获取错误的上下文若是有这种需求,可用定义自己的包,包含所有的错误类型。type Error interface { Caller() []CallerInfo Wraped() []error Code() []int //作为接口类型的扩展,增加调用栈信息,支持错误的多级嵌套包装,支持错误码格式 error private() } type CallerInfo struct { FuncName string FileName string FileLine int } //定义相应的辅助函数 func New (msg string) error //创建新的错误类型 func NewWithCode(code int, msg string) error//适用于http的创建 func Wrap(err error, msg string) //进行一层是错误包装,保留底层信息 func WrapWithCode(code int, err error, msg string) func formJson(json string)(Error, error) //传输使用 func ToJson(err error) string5.3 错误的错误返回在错误的返回时,没有错误最好直接返回nil。5.3.1 剖析异常recover()必须要和有异常的栈帧只隔一个栈帧时才能捕获异常。说人话就是要在defer(的一层)函数中调用才可用捕获异常。func main(){ defer func (){recover()}() //其他操作 }
2022年07月27日
336 阅读
0 评论
0 点赞
1
2