Posted on 2015-05-14 09:41:57 golang
摘要:本文分析了Golang的socket文件描述符和goroutine阻塞调度的原理。代码中大部分是Go代码,小部分是汇编代码。完整理解本文需要Go语言知识,并且用Golang写过网络程序。更重要的是,需要提前理解goroutine的调度原理。
…
网络轮询器是Golang中针对每个socket文件描述符建立的轮询机制。 此处的轮询并不是一般意义上的轮询,而是Golang的runtime在调度goroutine或者GC完成之后或者指定时间之内,调用epoll_wait获取所有产生IO事件的socket文件描述符。当然在runtime轮询之前,需要将socket文件描述符和当前goroutine的相关信息加入epoll维护的数据结构中,并挂起当前goroutine,当IO就绪后,通过epoll返回的文件描述符和其中附带的goroutine的信息,重新恢复当前goroutine的执行。
阅读全文Posted on 2015-04-24 08:55:17 golang
摘要:在Golang汇编快速指南这篇博客中,简单介绍了Golang中汇编的简单语法以及特殊之处。下面介绍Golang中的内置函数和相关操作代码的汇编实现,可以作为上篇博客的补充和实践。
汇编中过程调用的参数是通过栈来传递的,在栈上的布局如下:
参数3
参数2
参数1 <-FP
保存PC <-SP
...
...
package main
import (
"fmt"
)
type new_int int
var (
gobal_1 = "this is global var"
)
func main() {
auto_1 := "this is auto_1"
s0 := new(new_int)
s1 := make([]int, 10)
s2 := make([]int, 10)
append(s2, 9999)
fmt.Println(s1, s2)
fmt.Println(s0)
}
下面是汇编代码:
"".main t=1 size=1936 value=0 args=0x0 locals=0x128
// 定义函数main,栈帧大小为296字节,0字节的参数(无参数)
0x0000 00000 (builtin.go:13) TEXT "".main+0(SB),$296-0
// 将线程本地存储(thread local storage)传送到CX
0x0000 00000 (builtin.go:13) MOVQ (TLS),CX
// 下面是检查栈帧的大小是否超过目前分配的小
0x0009 00009 (builtin.go:13) LEAQ -168(SP),AX
0x0011 00017 (builtin.go:13) CMPQ AX,16(CX)
0x0015 00021 (builtin.go:13) JHI ,30
// 如果超过调用runtime.morestack_noctxt
0x0017 00023 (builtin.go:13) CALL ,runtime.morestack_noctxt(SB)
0x001c 00028 (builtin.go:13) JMP ,0
// 扩大栈帧
0x001e 00030 (builtin.go:13) SUBQ $296,SP
0x0025 00037 (builtin.go:13) FUNCDATA $0,gclocals·e14c7473fe07b0ccdc0fdfa1a770087b+0(SB)
0x0025 00037 (builtin.go:13) FUNCDATA $1,gclocals·7a70fcb413ec620f2a7a8c3ba5f394c1+0(SB)
// 局部变量auto_1
0x0025 00037 (builtin.go:14) LEAQ go.string."this is auto_1"+0(SB),BX
// string在golang中是由数据本身和其长度组成的
// 将数据的地址移动到BP
0x002c 00044 (builtin.go:14) MOVQ (BX),BP
// 移动BP到栈指针152字节的位置
0x002f 00047 (builtin.go:14) MOVQ BP,"".auto_1+152(SP)
// 将字符串长度移动到BP
0x0037 00055 (builtin.go:14) MOVQ 8(BX),BP
// 移动BP到栈指针160字节的位置
0x003b 00059 (builtin.go:14) MOVQ BP,"".auto_1+160(SP)
// new_int类型的初始化
// 将类型本身移动到BX
0x0043 00067 (builtin.go:17) MOVQ $type."".new_int+0(SB),BX
// 将BX移动到栈顶
0x004a 00074 (builtin.go:17) MOVQ BX,(SP)
0x004e 00078 (builtin.go:17) PCDATA $0,$1
// 调用runtime.newobject
0x004e 00078 (builtin.go:17) CALL ,runtime.newobject(SB)
// 将返回的结果移动到BX
0x0053 00083 (builtin.go:17) MOVQ 8(SP),BX
// 移动BP到栈指针80字节的位置
0x0058 00088 (builtin.go:17) MOVQ BX,"".s1+80(SP)
0x005d 00093 (builtin.go:17) NOP ,
// 创建slice s1
// 将类型移动到BX
0x005d 00093 (builtin.go:18) MOVQ $type.[]int+0(SB),BX
// 将BX(类型)移动到栈顶
0x0064 00100 (builtin.go:18) MOVQ BX,(SP)
// 将长度参数len移动到相对于栈顶8字节的位置
0x0068 00104 (builtin.go:18) MOVQ $10,8(SP)
// 将容量参数cap移动到相对于栈顶16字节的位置
0x0071 00113 (builtin.go:18) MOVQ $10,16(SP)
0x007a 00122 (builtin.go:18) PCDATA $0,$2
// 调用runtime.makeslice
0x007a 00122 (builtin.go:18) CALL ,runtime.makeslice(SB)
// 创建出的s1的参数分别保存在相对栈顶24, 32, 40字节的位置
0x007f 00127 (builtin.go:18) MOVQ 24(SP),DX
0x0084 00132 (builtin.go:18) MOVQ 32(SP),CX
0x0089 00137 (builtin.go:18) MOVQ 40(SP),BX
// CX中保存的是slice的len参数
0x008e 00142 (builtin.go:20) MOVQ CX,"".s2_len+64(SP)
0x0093 00147 (builtin.go:20) NOP ,
// 将slice的array, len, cap传送给s2
0x0093 00147 (builtin.go:21) MOVQ DX,"".s2+168(SP)
0x009b 00155 (builtin.go:21) MOVQ CX,"".s2+176(SP)
0x00a3 00163 (builtin.go:21) MOVQ BX,"".s2+184(SP)
0x00ab 00171 (builtin.go:21) MOVQ BX,AX
// 从BX中减去CX的值,结果保存在BX
0x00ae 00174 (builtin.go:21) SUBQ CX,BX
0x00b1 00177 (builtin.go:21) CMPQ BX,$1
0x00b5 00181 (builtin.go:21) JGE ,262
// 将类型传送到栈顶
0x00b7 00183 (builtin.go:21) MOVQ $type.[]int+0(SB),BX
0x00be 00190 (builtin.go:21) MOVQ BX,(SP)
// 将旧的slice传送到栈的第二个参数
0x00c2 00194 (builtin.go:21) MOVQ DX,"".autotmp_0001+240(SP)
0x00ca 00202 (builtin.go:21) MOVQ DX,8(SP)
// 将长度传送到栈上第三个参数(s2的长度)
0x00cf 00207 (builtin.go:21) MOVQ CX,"".autotmp_0001+248(SP)
0x00d7 00215 (builtin.go:21) MOVQ CX,16(SP)
// 将33行的BX的值重新保存到24(SP),即s2的array
0x00dc 00220 (builtin.go:21) MOVQ AX,"".autotmp_0001+256(SP)
0x00e4 00228 (builtin.go:21) MOVQ AX,24(SP)
// 将s2的长度置为1
0x00e9 00233 (builtin.go:21) MOVQ $1,32(SP)
// 调用growslice
0x00f2 00242 (builtin.go:21) PCDATA $0,$2
0x00f2 00242 (builtin.go:21) CALL ,runtime.growslice(SB)
// 3个返回值(sliceStruct)
0x00f7 00247 (builtin.go:21) MOVQ 40(SP),DX
0x00fc 00252 (builtin.go:21) MOVQ 48(SP),CX
0x0101 00257 (builtin.go:21) MOVQ 56(SP),AX
// 增加返回的结构中len参数长度,增加1
0x0106 00262 (builtin.go:21) MOVQ CX,SI
0x0109 00265 (builtin.go:21) INCQ ,SI
// 变址寻址,DX+CX*8
0x010c 00268 (builtin.go:21) LEAQ (DX)(CX*8),BX
// 将立即数9999传递到(BX)的内存位置
0x0110 00272 (builtin.go:21) MOVQ $9999,(BX)
0x0117 00279 (builtin.go:21) NOP ,
0x0117 00279 (builtin.go:21) MOVQ DX,"".autotmp_0001+240(SP)
0x011f 00287 (builtin.go:21) MOVQ SI,"".autotmp_0001+248(SP)
0x0127 00295 (builtin.go:21) MOVQ AX,"".autotmp_0001+256(SP)
// 将新的slice的参数传递给s2
0x012f 00303 (builtin.go:21) MOVQ DX,"".s2+168(SP)
0x0137 00311 (builtin.go:21) MOVQ SI,"".s2+176(SP)
0x013f 00319 (builtin.go:21) MOVQ AX,"".s2+184(SP)
0x0147 00327 (builtin.go:21) NOP ,
阅读全文
Posted on 2015-04-23 15:40:07 golang
摘要:这篇文档是对于Go编译器套件(6g, 8g, etc.)中不常用的汇编语言的快速预览,涵盖面不是很广泛。
Go的汇编语言基于Plan 9的汇编,Plan 9网站的页面上有详细描述。如果你想编写汇编语言,你应该读这篇文档,虽然它是Plan 9相关的。这边文档总结了汇编的语法,并且描述了使用汇编语言和Go程序交互时的特殊之处。
有一点是很重要的是,Go的汇编中没有直接体现出底层的机器。有些汇编细节能直接对应到机器,但有些不是。这是因为编译器套件在常规过程中不需要汇编语言。取而代之的是,编译器产生二进制的不完整的汇编指令集,链接器会完成它。实际上,链接器做了汇编指令的选择,所以当你看到类似于MOV这样的指令,链接器的实际操作可能不是一个移动指令,也许是清除或者载入。或者可能会根据指令的名字对应到真实的机器指令。总体上,机器相关的指令操作趋向于体现出真实的机器指令,但是一些通用的概念类似于移动内存数据、调用子例程、返回等操作就更抽象了。具体的细节和架构相关,我们为这种不精确性道歉。
阅读全文Posted on 2014-10-04 14:08:31 golang
Golang的Timer类,是一个普遍意义上的定时器,它有着普通定时器的一些特性,例如:
Golang的Timer在源码中,实现的方式是以一个小顶堆来维护所有的Timer集合。接着启动一个独立的goroutine,循环从小顶堆中的检测最近一个到期的Timer的到期时间,接着它睡眠到最近一个定时器到期的时间。最后会执行开始时设定的回调函数。Timer到期之后,会被Golang的runtime从小项堆中删除,并等待GC回收资源。
下面给出实际的代码:
package main
import (
"time"
"fmt"
)
func main() {
timer := time.NewTimer(3 * time.Second)
go func() {
<-timer.C
fmt.Println("Timer has expired.")
}()
timer.Stop()
time.Sleep(60 * time.Second)
}
timer.NewTimer()
会启动一个新的Timer实例,并开始计时。
我们启动一个新的goroutine,来以阻塞的方式从Timer的C这个channel中,等待接收一个值,这个值是到期的时间。并打印”Timer has expired.”
到现在看起来似乎没什么问题,但是当我们执行timer.Stop()
之后,3秒钟过去了,程序却没有打印那句话。说明执行timer.Stop()
之后,Timer自带的channel并没有关闭,而且这个Timer已经从runtime中删除了,所以这个Timer永远不会到期。
这会导致程序逻辑错误,或者更严重的导致goroutine和内存泄露。解决的办法是,使用timer.Reset()
代替timer.Stop()
来停止定时器。
package main
import (
"time"
"fmt"
)
func main() {
timer := time.NewTimer(3 * time.Second)
go func() {
<-timer.C
fmt.Println("Timer has expired.")
}()
//timer.Stop()
timer.Reset(0 * time.Second)
time.Sleep(60 * time.Second)
}
这样做就相当于给Timer一个0秒的超时时间,让Timer立刻过期。
Posted on 2014-10-04 03:51:00 golang
摘要:首先需要注意的是:golang1.3之后的版本,对于支持gdb调试存在很大的问题。产生这个问题的原因是,golang的runtime没有完整的被gdb支持。
最新比较完整支持gdb调试的版本是golang 1.2.2,但是也有个别问题存在。
为什么会出现以上种种问题,golang官网给出的解释是:
GDB does not understand Go programs well. The stack management, threading, and runtime contain aspects that differ enough from the execution model GDB expects that they can confuse the debugger, even when the program is compiled with gccgo. As a consequence, although GDB can be useful in some situations, it is not a reliable debugger for Go programs, particularly heavily concurrent ones. Moreover, it is not a priority for the Go project to address these issues, which are difficult. In short, the instructions below should be taken only as a guide to how to use GDB when it works, not as a guarantee of success.
翻译一下:
GDB不能很好的理解GO程序。堆栈管理,线程,而且runtime包含了非常不一样的执行模式,这不是GDB期望的,他们会扰乱调试器,即使go程序是使用gccgo编译的。结果就是,虽然GDB在某些场合下是有用的,但是对go程序来说并不是一个可靠的调试器。尤其是在大量并发的时候。而且,这不是Golang项目优先考虑的事情,这很困难。总而言之,下面的操作手册,只是当GDB正常工作的时候,引导你如何使用GDB,不能保证总是成功。
并且从google group讨论组和stackoverflow中,可以看到golang的多个版本对于GDB的支持都有这样那样的问题。 不过既然官方的手册都这么说了,我们也只有在合适的场合使用GDB吧。
默认情况下,编译过的二进制文件已经包含了 DWARFv3 调试信息,只要 GDB7.1 以上版本都可以进行调试。 在OSX下,如无法执行调试指令,可尝试用sudo方式执行gdb。
在编译go程序的时候,需要关闭内联优化:** -gcflags “-N -l”**。可以在go get/build/test
的时候指定这个参数。
有两种方式可以下断点:
Posted on 2014-06-08 12:43:50 golang
Package pprof serves via its HTTP server runtime profiling data in the format expected by the pprof visualization tool. For more information about pprof, see http://code.google.com/p/google-perftools/.
The package is typically only imported for the side effect of registering its HTTP handlers. The handled paths all begin with /debug/pprof/.
To use pprof, link this package into your program:
import _ "net/http/pprof"
If your application is not already running an http server, you need to start one. Add “net/http” and “log” to your imports and the following code to your main function:
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
Then use the pprof tool to look at the heap profile:
go tool pprof http://localhost:6060/debug/pprof/heap
Or to look at a 30-second CPU profile:
go tool pprof http://localhost:6060/debug/pprof/profile
Or to look at the goroutine blocking profile:
go tool pprof http://localhost:6060/debug/pprof/block
To view all available profiles, open http://localhost:6060/debug/pprof/ in your browser.
For a study of the facility in action, visit
http://blog.golang.org/2011/06/profiling-go-programs.html
Posted on 2014-03-10 23:47:03 golang
摘要:在Golang中字符串的一个坑这篇博文中,介绍了Golang中字符串与C中的字符串的不同之处:C中的字符串是以\x0
为结尾的字节序列,而Golang中的字符串则更严格,并不是以\x0
为结尾来判断,而是计算字符串变量的值中的所有字节。
Golang中的字符串在打印时,因为\x0
是不可打印字符,所以和C中的字符串在打印时没有任何区别。但是在一些需要按字节计算字符串的函数中,就会导致问题。通用的做法是,迭代字符串,只取得字节序列中的有效字节。
我们可以写一个函数来做这件事情,也可以用Golang的标准库中的bytes
库。bytes
库提供了对[]byte
类型的操作,提供的接口和对string
类型提供的接口类似。
Posted on 2014-03-05 22:44:56 golang
下面的总结是最近Golang开发中遇到的,和互联网上搜集到的坑,会持续更新:
字符串不是以\x0
结束作为判断的
无锁队列只是在单核下,多核同样需要锁,需要线程间调度
格式化时间字符串只能用2006 01 02 15 04 05
(时间原点), 并且在time/format.go
写死了:
2006-01-02T15:04:05Z07:00
其实这是有意义的,每个字段正好对应:
1 2 3 4 5 6 7
月 日 时 分 秒 年 时区
当多个channel都处于就绪状态时,激活channel是随机的,不能按照顺序判断
os.exit(1)
执行后,defer是没有作用的
Goroutine只有非阻塞的IO调用阻塞时,才会发生Goroutine切换,如果是系统调用:
Golang会直接启动内核级线程
,来实现异步。。。所以避免大量启动系统调用。。。
内存泄漏的一个坑 如果你的代码中出现这样,恭喜你,你已经在坑里。
func somefunc() {
for {
// ...
defer something.Cleanup()
}
}
原因是这里的defer没有机会被执行到。 简单的解决办法是将这里的逻辑放到一个方法中去。
Posted on 2014-02-23 13:41:38 golang
在C语言中,字符串的内存模型定义为以NUL
(\x0)结尾的字节数组。这是为大家所熟知的。
但是在Golang中并不是如此,Golang中的字符串abc
和abc\x0\x0
并不相当,所以说Golang明确规定了字符串的长度,而不是以\x0
为结尾来判断的。
下面看示例代码:
package main
import (
"fmt"
"os"
)
func main() {
var a[5]byte = [5]byte{'a','b','c'}
var b[]byte = []byte{'a','b','c'}
fmt.Printf("len(a): %d, %q\n", len(a), a)
fmt.Printf("len(b): %d, %q\n", len(b), b)
slice_a := a[:]
str_a := string(slice_a)
str_b := string(b)
fmt.Printf("len(str_a): %d, %q:%s\n", len(str_a), str_a, str_a)
fmt.Printf("len(str_b): %d, %q:%s\n", len(str_b), str_b, str_b)
if str_a == str_b {
fmt.Println("str_a == str_b")
} else {
fmt.Println("str_a != str_b")
}
file, err := os.Create(str_a)
if err != nil {
fmt.Println(err)
}
fmt.Println(file)
}
代码输出为:
len(a): 5, "abc\x00\x00"
len(b): 3, "abc"
len(str_a): 5, "abc\x00\x00":abc
len(str_b): 3, "abc":abc
str_a != str_b
open abc: invalid argument
<nil>
数组a
长度为5,最后两个字节为\x00
。切片b
长度为3,有效内容为abc
。
分别将a和b转换为string类型后,数组a
中包含的\x00
也被保留下来,所以在对比时,这两者的长度不可能相等的。
当然这还不是最关键的,问题处在当用str_a作为文件名创建文件时file, err := os.Create(str_a)
,系统会报错:open abc: invalid argument
。说明这不是一个有效的路径名称。
Golang是一种强类的语言,对于数组来说[3]byte
和[5]byte
并不是同一个类型,不能通用。
当然,数组和切片也更不可能是同一个类型。
解决办法是,迭代数组中的字节,跳过为0
的字节:
func GetValidByte(src []byte) []byte {
var str_buf []byte
for _, v := range src {
if v != 0 {
str_buf = append(str_buf, v)
}
}
return str_buf
}
Posted on 2014-02-10 08:43:22 golang
摘要:Golang有自带的net/rpc和net/rpc/jsonrpc,前者是基于Gob编码协议的RPC,后者在前者基础上使用JSON编码。
在Python的世界,RESTful的框架比比皆是。由于Golang标准库没有自带,但有许多优秀的第三方库,例如下面要介绍的这个库,go-json-rest.
阅读全文