如何监控 golang 变量回收程序的垃圾回收

温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
small superman
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
阅读(270)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
在LOFTER的更多文章
loftPermalink:'',
id:'fks_',
blogTitle:'golang-流程控制语句 for循环',
blogAbstract:'package mainimport (\t\"fmt\")func main() {\t//Go 只有一种循环结构for循环。\t//基本的 for 循环除了没有了 ( ) 之外(,看起来跟 C 或者 Java 中做的一样,\t//而 { } 是必须的\tfor i := 0; i & 100; i++ {\t\tfmt.Println(i)\t}\t//死循环:如果省略了循环条件,\t//循环就不会结束,因此可以用更简洁地形式表达死循环。\t// for {\t//\t// }}for的condition在每执行一次循环体时便会执行一次,因此在实际开发过程中需要注意不要让condition中计算简单而不是复杂。',
blogTag:'',
blogUrl:'blog/static/',
isPublished:1,
istop:false,
modifyTime:8,
publishTime:6,
permalink:'blog/static/',
commentCount:0,
mainCommentCount:0,
recommendCount:0,
bsrk:-100,
publisherId:0,
recomBlogHome:false,
currentRecomBlog:false,
attachmentsFileIds:[],
groupInfo:{},
friendstatus:'none',
followstatus:'unFollow',
pubSucc:'',
visitorProvince:'',
visitorCity:'',
visitorNewUser:false,
postAddInfo:{},
mset:'000',
remindgoodnightblog:false,
isBlackVisitor:false,
isShowYodaoAd:false,
hostIntro:'small superman',
hmcon:'0',
selfRecomBlogCount:'0',
lofter_single:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}对于程序及服务的控制,本质上而言就是正确的启动,并可控的停止或退出。在go语言中,其实就是程序安全退出、服务控制两个方面。核心在于系统信号获取、Go Concurrency Patterns、以及基本的代码封装。程序安全退出执行代码非安全写法在代码部署后,我们可能因为服务配置发生变化或其他各种原因,需要将服务停止或者重启。通常就是for循环阻塞,运行代码,然后通过control+C或者kill来强制退出。代码如下://file svc1.gopackage mainimport (
&time&)//当接收到Control+c,kill -1,kill -2,kill -9 均无法正常执行defer函数func main() {
fmt.Println(&application is begin.&)
//以下代码不会执行
defer fmt.Println(&application is end.&)
time.Sleep(time.Second)
fmt.Println(&application is running.&)
}}这种方式简单粗暴,很多时候基本也够用。但这种情况下,程序是不会执行defer的代码的,因此无法正确处理结束操作,会丢失一些很关键的日志记录、消息通知,非常不安全的。这时,需要引入一个简单的框架,来执行退出。执行代码的基本:信号拦截由于go语言中的关键字go很好用,通过标准库,我们可以很优雅的实现退出信号的拦截://file svc2.gopackage mainimport (
&os/signal&
&os&)//当接收到Control+c,kill -1,kill -2 的时候,都可以执行执行defer函数// kill -9依然不会正常退出。func main() {
fmt.Println(&application is begin.&)
//当程序接受到退出信号的时候,将会执行
defer fmt.Println(&application is end.&)
//协程启动的匿名函数,模拟业务代码
go func(){
time.Sleep(time.Second)
fmt.Println(&application is running.&)
//捕获程序退出信号
msgChan:=make(chan os.Signal,1)
signal.Notify(msgChan,os.Interrupt,os.Kill)
&-msgChan}此时,我们实现了程序退出时的信号拦截,补充业务代码就可以了。但实际业务逻辑至少涉及到初始化、业务处理、退出三大块,代码量多了,会显得比较混乱,这就需要规范代码的结构。执行代码的改进:信号拦截包装器考虑上述情况,我们将正常的程序定义为:Init: 系统初始化,比如识别操作系统、初始化服务发现Consul、Zookeper的agent、数据库连接池等。 Start:程序主要业务逻辑,包括但不限于数据加载、服务注册、具体业务响应。 Stop: 程序退出时的业务,主要包括内存数据存储、服务注销。基于这个定义,之前的svc2.go仅保留业务代码的情况下,可以这样改写://file svc3.gopackage mainimport (
&study1/svc&)type Program struct {}func (p *Program) Start()error
fmt.Println(&application is begin.&)
//必须非阻塞,因此通过协程封装。
go func(){
time.Sleep(time.Second)
fmt.Println(&application is running.&)
return nil}func (p *Program)Init()error{
//just demon,do nothing
return nil}func (p *Program) Stop() error {
fmt.Println(&application is end.&)
return nil}//当接收到Control+C,kill -1,kill -2 的时候,都可执行defer函数// kill -9依然不会正常退出。func main() {
p:=&Program{}
svc.Run(p)}上诉代码中的Program的Init、Start、Stop事实上是实现了相关的接口定义,该接口在svc包中,被Run方法使用。代码如下://file svc.gopackage svcimport (
&os/signal&)//标准程序执行和退出的执行接口,运行程序要实现接口定义的方法type Service interface {
Init() error
//当程序启动运行的时候,需要执行的代码。不得阻塞。
Start() error
//程序退出的时候,需要执行的代码。不得阻塞。
Stop() error}var msgChan = make(chan os.Signal, 1)// 程序运行、退出的包装容器,主程序直接调用。func Run(service Service) error {
if err := service.Init(); err != nil {
return err
if err := service.Start(); err != nil {
return err
signal.Notify(msgChan, os.Interrupt, os.Kill)
return service.Stop()}// 通常不需要调用,特殊情况下,在程序内其他模块中,需要通知程序退出才会使用。func Interrupt(){
msgChan&-os.Interrupt}这段代码中,svg包的Run只会被唯一的main调用。为了支持其他退出模式,比如用户敲入字符命令的退出,因此加入了&后门&&&Interrupt方法。后边会有具体的使用案例。由于一个进程只会有一个svg.Service的实例,通常情况下足以使用。单机多服务的应用启动、退出框架在网络应用,可能会有更复杂的情况,我们需要考虑:程序启动 程序不退出的情况下,多服务启动、并行运行与退出 程序退出,并清理运行中的服务可以做一个简单的Demon程序,来实现以上三点,其中,程序退出可以通过键盘输入命令,也可以Control+D。基于golang1.7,我们可以采用以下知识点:利用cancelContext来控制服务的退出 利用之前实现的svc来实现程序的安全退出 利用os.Stdin来获取键盘输入命令来模拟服务加载与退出的消息驱动。实际可能是网络rpc或http数据触发golang1.7的context包我们知道,当通道chan被close之后,任何&-chan都会得到立即执行。如果不清楚,可以查阅相关资料或写个测试代码,最好研读golang的官方资料:Go Concurrency Patterns: Pipelines and cancellation。利用这个特征,我们可以通过golang1.7标准库新增的context包,通过注入的方式来实现全局或单个服务的控制。context中定义了Context接口,我们通过几种不同的方法来获取不同的实现。包括:WithDeadline\WithTimeout,获取到基于时间相关的退出句柄,以控制服务退出。 WithCancel,获取到cancelFunc句柄,以控制服务的退出。 WithValue,获取到k-v键值对,实现类似于session信息保存的业务支持。 Background\TODO,conext的根,通常作为以上三种方法的parent。context包不是新东西,2014年就已经在google.org/x/net中,作为扩展库被很多开源项目使用(GIN、IRIS等等)。其CSP的应用方式非常值得进一步研读。捕获键盘输入通过os.stdin来获取键盘输入,其解析需要bufilo.Reader来协助处理。通常代码格式就是://...//初始化键盘读取reader:=bufilo.NewReader(os.Stdin)//阻塞,直到敲入Enter键input, _, _ := reader.ReadLine()command:=string(input)//...示例代码有了这两个概念之后,就可以很方便的实现一个简单的微服务加载、退出的框架。参考代码如下://file svc4.gopackage mainimport (
&study1/svc&
&time&)type Program struct {
context.Context
context.CancelFunc
cancelFunc map[string]context.CancelFunc
WaitGroupWrapper}func main() {
p := &Program{
cancelFunc: make(map[string]context.CancelFunc),
p.ctx, p.exitFunc = context.WithCancel(context.Background())
svc.Run(p)}func (p *Program) Init() error {
//just demon,do nothing
return nil}func (p *Program) Start() error {
fmt.Println(&本程序将会根据输入,启动或终止服务。&)
reader := bufio.NewReader(os.Stdin)
go func() {
fmt.Println(&程序退出命令:服务启动命令:-[name];服务停止命令:-[name]。请注意大小写!&)
input, _, _ := reader.ReadLine()
command := string(input)
switch command {
case &exit&:
goto OutLoop
command, name, err := splitInput(input)
if err != nil {
fmt.Println(err)
switch command {
case &start&, &s&:
newctx, cancelFunc := context.WithCancel(p.ctx)
p.cancelFunc[name] = cancelFunc
p.wg.Wrap(func() {
Func(newctx, name)
case &cancel&, &c&:
cancelFunc, founded := p.cancelFunc[name]
if founded {
cancelFunc()
//由于程序退出被Run的os.Notify阻塞,因此调用以下方法通知退出代码执行。
svc.Interrupt()
return nil}func (p *Program) Stop() error {
p.exitFunc()
p.wg.Wait()
fmt.Println(&所有服务终止,程序退出!&)
return nil}//用来转换输入字符串为输入命令func splitInput(input []byte) (command, name string, err error) {
line := string(input)
strs := strings.Split(line, &-&)
if strs == nil || len(strs) != 2 {
err = errors.New(&输入不符合规则。&)
command = strs[0]
name = strs[1]
return}// 一个简单的循环方法,模拟被加载、释放的微服务func Func(ctx context.Context, name string) {
case &-ctx.Done():
goto OutLoop
case &-time.Tick(time.Second * 2):
fmt.Printf(&%s is running.\n&, name)
fmt.Printf(&%s is end.\n&, name)}//WaitGroup封装结构type WaitGroupWrapper struct {
sync.WaitGroup}func (w *WaitGroupWrapper) Wrap(f func()) {
go func() {
}()}代码运行的时候,可以:通过输入&s-&或者&start-&+服务名,来启动一个服务 用&c-&或&cancel-&+服务名,来退出指定服务 可以用 &exit&或者Control+C、kill来退出程序(除了kill -9)。在此基础上,还可以利用context包实现服务超时退出,利用for range限制服务数量,利用HTTP实现微服务RestFUL信息驱动。由于扩展之后代码增加,显得冗余,这里不再赘述。就爱阅读网友整理上传,为您提供最全的知识大全,期待您的分享,转载请注明出处。
欢迎转载:
推荐:    Golang | 程序师Golang控制goroutine的启动与关闭
来源:博客园
  最近在用golang做项目的时候,使用到了goroutine。在golang中启动协程非常方便,只需要加一个go关键字:
   

 go myfunc(){

      //do something
 }()

但是对于一些长时间执行的任务,例如:

 go loopfunc(){
for{
      //do something repeat
}()

在某些情况下,需要退出时候却有些不方便。举个例子,你启动了一个协程,长时间轮询处理一些任务。当某种情况下,需要外部通知,主动结束这个循环。发现,golang并没有像java那样中断或者关闭线程的interrupt,stop方法。于是就想到了channel,通过类似信号的方式来控制goroutine的关闭退出(实际上并不是真的直接关闭goroutine,只是把一些长时间循环的阻塞函数退出,然后让goroutine自己退出),具体思路就是就是对于每个启动的goroutine注册一个channel。为了方便后续使用,我封装了一个简单的库:
原理比较简单,这里不详细说了,直接看源码就可以了。具体使用示例:
 

package main

import (
"fmt"
"/scottkiss/grtm"
"time"
)

func myfunc() {
fmt.Println("do something repeat by interval 4 seconds")
time.Sleep(time.Second * time.Duration(4))
}

func main() {
gm := grtm.NewGrManager()
gm.NewLoopGoroutine("myfunc", myfunc)
fmt.Println("main function")
time.Sleep(time.Second * time.Duration(40))
fmt.Println("stop myfunc goroutine")
gm.StopLoopGoroutine("myfunc")
time.Sleep(time.Second * time.Duration(80))
}


  
免责声明:本站部分内容、图片、文字、视频等来自于互联网,仅供大家学习与交流。相关内容如涉嫌侵犯您的知识产权或其他合法权益,请向本站发送有效通知,我们会及时处理。反馈邮箱&&&&。
学生服务号
在线咨询,奖学金返现,名师点评,等你来互动[译] Golang 实时垃圾回收理论和实践 - 推酷
[译] Golang 实时垃圾回收理论和实践
Golang实时垃圾回收理论和实践
每天,Pusher实时发送数十亿条消息:从消息源到达目的地控制在100ms内。 我们如何实现这一目标? 一个关键因素是Go的低延迟垃圾回收器。
垃圾收集器是实时系统的祸根,因为他们会暂停程序。 因此,在设计我们的新消息总线时,我们仔细选择了语言。 虽然Go强调低延迟垃圾回收,但我们很警惕:Go真的做到这一点吗? 如果能做到,效果如何呢?
在这篇博文中,我们会审视Go的垃圾收集器。 我们将看看它是如何工作的(三色算法),为什么它有这样短的GC暂停,最重要的是,它是否工作(对其进行GC基准测试,并与其他语言进行比较)。
From Haskell to Go
我们一直在构建的系统是一个带有已发布消息内存存储的pub / sub消息总线。 Go中的这个版本是Haskell版本的重新实现。在发现GHC的垃圾收集器的延迟问题后,我们在5月停止了在Haskell版本的工作。
我们发布了Haskell版本的细节。基本问题是GHC的暂停时间与工作集的大小(即内存中的对象数量)成正比。在我们的例子中,我们在内存中有很多对象,这导致了几百毫秒的暂停时间。任何GC在完成收集之前都会阻塞程序。
Go与GHC的STW(stop-the-world)收集器不同,Go的垃圾回收器与程序同时运行,这使得避免更长的停顿时间成为可能。我们对Go的低延迟垃圾回收感到鼓舞,并发现随着版本改进延迟得到进一步降低。
并发垃圾收集器如何工作?
Go的GC如何实现并发?其 核心是三色标记扫描算法。 它使GC与程序同时运行; 这意味着暂停时间成为调度问题。 调度程序可以配置为仅在短时间内运行GC收集,与程序交叉运行。 这对我们的低延迟要求是个好消息!
GC仍然有两个暂停阶段:对根对象的初始堆栈扫描,以及标记终止阶段。 令人兴奋的是,这个终止阶段最近已经消除。 我们将在后面讨论这个优化。 在实践中,我们发现即使具有非常大的堆,这些阶段的暂停时间也可以&1ms。
使用并发GC,也有可能在多个处理器上并行运行GC。
延迟VS吞吐量
如果使用并发GC可以在大堆上得到低得多的延迟,为什么要使用stop-the-world收集器?是不是Go的并发垃圾收集器比GHC的stop-the-world收集器更好吗?
不必要。低延迟有成本。最重要的成本是减少吞吐量。并发性需要额外的工作来同步和复制,这会减少程序正常运行的时间。 GHC的垃圾收集器针对吞吐量进行了优化,但Go收集器对延迟进行优化。在Pusher,我们关心延迟,所以这是一个对我们来说很好的折衷。
并发垃圾收集器的第二个成本是不可预测的堆增长。程序可以在GC运行时分配任意数量的内存。这意味着GC必须在堆达到目标最大大小之前运行。但是如果GC运行得太快,那么将执行更多的收集工作。这种权衡是棘手的(Austin Clements提供了一个很好的概述)。在Pusher,这种不可预测性不是一个问题;我们的程序倾向于以可预测的恒定速率分配内存。
在实践中如何?
到目前为止,Go的GC看起来很适合我们的延迟要求。 但它在实践中如何?
今年早些时候,当调查Haskell实现的暂停时间时,我们为测量暂停创建了一个基准。 基准程序重复地将消息推送到大小受限的缓冲区中。 旧消息不断地过期并变成垃圾。 堆大小保持很大,这很重要,因为必须遍历堆才能检测哪些对象仍被引用。 这就是为什么GC运行时间与它们之间的活对象/指针的数量成正比。
这里是Go中的基准,其中缓冲区被建模为数组:
package main
windowSize = 200000
message []byte
buffer [windowSize]message
var worst time.Duration
func mkMessage(n int) message {
m := make(message, 1024)
for i := range m {
m[i] = byte(n)
func pushMsg(b *buffer, highID int) {
start := time.Now()
m := mkMessage(highID)
(*b)[highID%windowSize] = m
elapsed := time.Since(start)
if elapsed & worst {
worst = elapsed
func main() {
var b buffer
for i := 0; i & msgC i++ {
pushMsg(&b, i)
fmt.Println(&Worst push time: &, worst)
根据James Fisher的博客,Gabriel Scherer写了一篇后续博客文章,将原来的Haskell基准与OCaml和Racket的版本进行比较。 他创建了一个包含这些基准的 仓库 ,Santeri Hiltunen添加了一个 Java版本 。 我决定将基准移植到Go,以便比较它的效果。
不用多说,这里是我的系统上的基准测试结果:
在这里是Java表现很差,OCaml表现非常好。 OCaml的~3ms暂停时间是由于OCaml用旧一代的增量GC算法。 (我们不选择OCaml的主要原因是它的并发支持很差)。
如你所见,Go执行顺利,暂停时间约为7ms。 这达到我们的要求。
一些注意事项
警惕基准!不同的运行时针对不同的用例和不同的平台进行了优化。然而,由于我们有明确的延迟要求,并且这个基准代表我们的用例,它表明Go对我们来说很好。
map vs array - 最初我们的基准是基于从map中插入和删除项目。然而,Go的垃圾收集器在处理大map的时候有 bug ,这掩盖了我们的结果。为此,我们决定切换为可变数组的map。Go Map bug在Go 1.8中已经修复,但是并不是所有的基准都被移植到1.8,这就是为什么我要区分这两者。尽管如此,没有理由期望GC时间比map(除了错误或不良实现)更糟糕。
手动vs rts计时 - 作为第二个警告,基准在计时方面不同:一些基准使用手动计时器,但其他使用运行时系统统计。存在此差异,因为某些运行时不会使该统计信息可用(例如在Go中)。我们还担心,打开profiling会对影响一些语言的垃圾收集器。为此,我们将所有基准移植到手动计时。
最后一个警告是基准实现中的最坏情况。有一种情况, insert/delete map操作可能不利地影响定时,这是切换到使用简单数组的另一个原因。
请为我们的基准贡献更多的语言!这个简单的基准是非常通用的,在选择语言时是一个重要的基准。你想看看$ YOUR_LANGUAGE的GC执行情况,然后请提交PR! :)我会特别感兴趣的是知道为什么Java暂停时间是如此糟糕,因为按理论它应该更好。
为什么Go的结果不好?
使用已修复mapbug的编译器,或使用数组时,我们得到暂停时间~7ms。 这是非常好的,但是根据Go团队在演示幻灯片标题为“1.5 Garbage Benchmark Latency”的基准测试结果,我们预计我们的堆大小为200MB时暂停大约1m(GC次数往往与 指针数量而不是字节数,但是它们不能提供该信息)。 Twitch团队使用Go1.7达到约1ms的暂停时间(尽管它们不清楚堆对象的数量)。
我在golang-nuts邮件列表问这个原因。 Rhys Hilter的想法是,这些暂停时间可能是由这个当前未定的错误引起的,GC中的空闲标记worker可能会阻止程序,即使有工作要做。 为了尝试并确认这一点,我启动了go tool trace [3],它可视化程序运行时行为。
从这个例子可以看出,有一个12ms的周期,其中背景标记woker正在所有四个处理器上运行,阻塞程序。这使我强烈怀疑我遇到上述错误。
到目前为止,我很高兴看到基准测试的现有暂停时间满足要求,但我也保持关注,以解决上述问题。
如前所述,Go团队最近宣布了一项改进措施,导致GC暂停时间小于1ms。它燃起我的希望,但我很快就意识到这个优化是去掉了stw阶段,swt阶段在我使用的基准下已经&1ms。我们的暂停时间主要是由GC的并发阶段引起的。
尽管如此,这是一个值得欢迎的改进,并表明团队继续关注并改进GC的延迟。这种优化的技术描述本身是一个有趣的读物。
这项调查的关键是GC是针对更低的延迟或更高的吞吐量进行优化。程序可能执行更好或更差在这取决于您的程序的堆使用率。 (有很多的对象吗?他们有长或短的生命吗?)
重要的是要了解底层的GC算法,以决定它是否适合您的用例。在实践中测试GC实现也很重要。您的基准测试应该与您打算实现的程序具有相同的堆使用率。这将在实践中验证GC实现的有效性。正如我们所看到的,Go的实现不是没有bug的,但在我们的情况下,问题是可以接受的。我想在更多的语言中看到相同的基准,如果你想贡献的话:)
尽管存在一些问题,Go的GC与其他GC语言相比表现良好。 Go团队一直在改进延迟,并继续这样做。我们对Go语言GC的理论和实践感到满意。
原文: /golangs-real-time-gc-in-theory-and-practice/
介绍一个跟编程密切相关的一个技术活动,GIAC 全球互联网架构大会将在 12 月 16 ~ 17 日在北京举行。
参加 GIAC,认识更多技术牛人,最后一周优惠,购买双日套票,高可用架构后花园会员最低仅需 900 元,非会员最低只需 1,260 元,点击阅读原文进入购买页面。
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致

我要回帖

更多关于 golang gc 回收时间 的文章

 

随机推荐