如何优雅地等待所有的goroutine退出
goroutine和channel是Go语言非常棒的特色,它们提供了一种非常轻便易用的并发能力。但是当您的应用进程中有很多goroutine的时候,如何在主流程中等待所有的goroutine 退出呢?
为资兴等地区用户提供了全套网页设计制作服务,及资兴网站建设行业解决方案。主营业务为成都网站建设、成都做网站、资兴网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
1 通过Channel传递退出信号
Go的一大设计哲学就是:通过Channel共享数据,而不是通过共享内存共享数据。主流程可以通过channel向任何goroutine发送停止信号,就像下面这样:
func run(done chan int) {
for {
select {
case -done:
fmt.Println("exiting...")
done - 1
break
default:
}
time.Sleep(time.Second * 1)
fmt.Println("do something")
}
}
func main() {
c := make(chan int)
go run(c)
fmt.Println("wait")
time.Sleep(time.Second * 5)
c - 1
-c
fmt.Println("main exited")
}
这种方式可以实现优雅地停止goroutine,但是当goroutine特别多的时候,这种方式不管在代码美观上还是管理上都显得笨拙不堪。
2 使用waitgroup
sync包中的Waitgroup结构,是Go语言为我们提供的多个goroutine之间同步的好刀。下面是官方文档对它的描述:
A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for.
Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.
通常情况下,我们像下面这样使用waitgroup:
创建一个Waitgroup的实例,假设此处我们叫它wg
在每个goroutine启动的时候,调用wg.Add(1),这个操作可以在goroutine启动之前调用,也可以在goroutine里面调用。当然,也可以在创建n个goroutine前调用wg.Add(n)
当每个goroutine完成任务后,调用wg.Done()
在等待所有goroutine的地方调用wg.Wait(),它在所有执行了wg.Add(1)的goroutine都调用完wg.Done()前阻塞,当所有goroutine都调用完wg.Done()之后它会返回。
那么,如果我们的goroutine是一匹不知疲倦的牛,一直孜孜不倦地工作的话,如何在主流程中告知并等待它退出呢?像下面这样做:
type Service struct {
// Other things
ch chan bool
waitGroup *sync.WaitGroup
}
func NewService() *Service {
s := Service{
// Init Other things
ch: make(chan bool),
waitGroup: sync.WaitGroup{},
}
return s
}
func (s *Service) Stop() {
close(s.ch)
s.waitGroup.Wait()
}
func (s *Service) Serve() {
s.waitGroup.Add(1)
defer s.waitGroup.Done()
for {
select {
case -s.ch:
fmt.Println("stopping...")
return
default:
}
s.waitGroup.Add(1)
go s.anotherServer()
}
}
func (s *Service) anotherServer() {
defer s.waitGroup.Done()
for {
select {
case -s.ch:
fmt.Println("stopping...")
return
default:
}
// Do something
}
}
func main() {
service := NewService()
go service.Serve()
// Handle SIGINT and SIGTERM.
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
fmt.Println(-ch)
// Stop the service gracefully.
service.Stop()
}
golang elasticsearch 文档操作(CRUD) --- 2022-04-02
本节主要介绍go语言对Elasticsearch文档的基础操作:创建、查询、更新、删除。
为了方便演示文档的CRUD操作,我们先定义索引的struct结构
根据文档ID,查询文档
通过多个Id批量查询文档,对应ES的multi get
根据id更新文档
支持批量更新文档内容
提示: 复杂查询条件,请参考 go es查询用法
go语言简体中文编码包怎么用
二进制包安装的话:如果安装在了默认位置例如/usr/local/go (Windows系统:c:\Go)这个时候可以不需要设置GOROOT,如果改变了安装的这个默认目录,那么就需要设置GOROOT
GOROOT的概念有点Go安装目录的意思
GOPATH是一个开发环境目录的意思,下面必须包含bin、pkg、src,然后再src下面新建项目就可以了
你在没有设置GOPATH的情况下,也可以把项目建在GOROOT/src下面,其实和GOPATH的概念类似
《Go程序设计语言中文版》pdf下载在线阅读全文,求百度网盘云资源
《Go程序设计语言中文版》百度网盘pdf最新全集下载:
链接:
?pwd=0cii 提取码:0cii
简介:本书由《C程序设计语言》的作者Kernighan和谷歌公司Go团队主管Alan Donovan联袂撰写,是学习Go语言程序设计的指南。本书共13章,主要内容包括:Go的基础知识、基本结构、
基本数据类型、复合数据类型、函数、方法、接口、goroutine、通道、共享变量的并发性、包、go工具、测试、反射等。
本书适合作为计算机相关专业的教材,也可供Go语言爱好者阅读
golang配制高性能sql.DB
有很多教程是关于Go的sql.DB类型和如何使用它来执行SQL数据库查询的。但大多数内容都没有讲述 SetMaxOpenConns() , SetMaxIdleConns() 和 SetConnMaxLifetime()方法, 您可以使用它们来配置sql.DB的行为并改变其性能。
转自:
整理:go语言中文文档:
在本文我将详细解释这些设置的作用,并说明它们所能产生的(积极和消极)影响。
一个sql.DB对象就是一个数据库连接池,它包含“正在用”和“空闲的”连接。一个正在用的连接指的是,你正用它来执行数据库任务,例如执行SQL语句或行查询。当任务完成连接就是空闲的。
当您创建sql.DB执行数据库任务时,它将首先检查连接池中是否有可用的空闲连接。如果有可用的连接,那么Go将重用现有连接,并在执行任务期间将其标记为正在使用。如果池中没有空闲连接,而您需要一个空闲连接,那么Go将创建一个新的连接。
默认情况下,在同一时间打开连接的数量是没有限制(包含使用中+空闲)。但你可以通过SetMaxOpenConns()方法实现自定义限制,如下所示:
在这个示例代码中,连接池现在有5个并发打开的连接数。如果所有5个连接都已经被标记为正在使用,并且需要另一个新的连接,那么应用程序将被迫等待,直到5个连接中的一个被释放并变为空闲。
为了说明更改MaxOpenConns的影响,我运行了一个基准测试,将最大打开连接数设置为1、2、5、10和无限。基准测试在PostgreSQL数据库上执行并行的INSERT语句,您可以在这里找到代码。测试结果:
对于这个基准测试,我们可以看到,允许打开的连接越多,在数据库上执行INSERT操作所花费的时间就越少(打开的连接数为1时,执行速度3129633ns/op,而无限连接:531030ns/op——大约快了6倍)。这是因为允许打开的连接越多,可以并发执行的数据库查询就越多。
默认情况下,sql.DB允许连接池中最多保留2个空闲连接。你可以通过SetMaxIdleConns()方法改变它,如下所示:
从理论上讲,允许池中有更多的空闲连接将提高性能,因为这样就不太可能从头开始建立新连接——因此有助于提升数据库性能。
让我们来看看相同的基准测试,最大空闲连接设置为none, 1,2,5和10:
当MaxIdleConns设置为none时,必须为每个INSERT从头创建一个新的连接,我们可以从基准测试中看到,平均运行时和内存使用量相对较高。
只允许保留和重用一个空闲连接对基准测试影响特别明显——它将平均运行时间减少了大约8倍,内存使用量减少了大约20倍。继续增加空闲连接池的大小会使性能变得更好,尽管改进并不明显。
那么,您应该维护一个大的空闲连接池吗?答案取决于应用程序。重要的是要意识到保持空闲连接是有代价的—它占用了可以用于应用程序和数据库的内存。
还有一种可能是,如果一个连接空闲时间太长,那么它可能会变得不可用。例如,MySQL的wait_timeout设置将自动关闭任何8小时(默认)内未使用的连接。
当发生这种情况时,sql.DB会优雅地处理它。坏连接将自动重试两次,然后放弃,此时Go将该连接从连接池中删除,并创建一个新的连接。因此,将MaxIdleConns设置得太大可能会导致连接变得不可用,与空闲连接池更小(使用更频繁的连接更少)相比,会占有更多的资源。所以,如果你很可能很快就会再次使用,你只需保持一个空闲的连接。
最后要指出的是,MaxIdleConns应该总是小于或等于MaxOpenConns。Go强制执行此操作,并在必要时自动减少MaxIdleConns。
现在让我们看看SetConnMaxLifetime()方法,它设置连接可重用的最大时间长度。如果您的SQL数据库也实现了最大连接生命周期,或者—例如—您希望方便地在负载均衡器后交换数据库,那么这将非常有用。
你可以这样使用它:
在这个例子中,所有的连接都将在创建后1小时“过期”,并且在过期后无法重用。但注意:
从理论上讲,ConnMaxLifetime越短,连接过期的频率就越高——因此,需要从头创建连接的频率就越高。为了说明这一点,我运行了将ConnMaxLifetime设置为100ms、200ms、500ms、1000ms和无限(永远重用)的基准测试,默认设置为无限打开连接和2个空闲连接。这些时间段显然比您在大多数应用程序中使用的时间要短得多,但它们有助于很好地说明行为。
在这些特定的基准测试中,我们可以看到,与无限生存期相比,在100ms生存期时内存使用量增加了3倍以上,而且每个INSERT的平均运行时也稍微长一些。
如果您在代码中设置了ConnMaxLifetime,那么一定要记住连接将过期(随后重新创建)的频率。例如,如果您总共有100个连接,而ConnMaxLifetime为1分钟,那么您的应用程序可能每秒钟杀死和重新创建1.67个连接(平均值)。您不希望这个频率太大,最终会阻碍性能,而不是提高性能。
最后,如果不说明超过数据库连接数量的硬限制将会发生什么,那么本文就不完整了。 为了说明这一点,我将修改postgresql.conf文件,这样总共只允许5个连接(默认是100个)…
然后在无限连接的情况下重新运行基准测试……
一旦达到5个连接的硬限制,数据库驱动程序(pq)立即返回一个太多客户端连接的错误消息,而无法完成INSERT。为了防止这个错误,我们需要将sql.DB中打开连接的最大总数(正在使用的+空闲的)设置为低于5。像这样:
现在,sql.DB在任何时候最多只能创建3个连接,基准测试运行时应该不会出现任何错误。但是这样做需要注意:当达到开放连接数限制,并且所有连接都在使用时,应用程序需要执行的任何新的数据库任务都将被迫等待,直到连接标记为空闲。例如,在web应用程序的上下文中,用户的HTTP请求看起来会“挂起”,甚至在等待数据库任务运行时可能会超时。
为了减轻这种情况,你应该始终在一个上下文中传递。在调用数据库时,启用上下文的方法(如ExecContext()),使用固定的、快速的超时上下文对象。
总结
1、根据经验,应该显式设置MaxOpenConns值。这应该小于数据库和基础设施对连接数量的硬性限制。
2、一般来说,更高的MaxOpenConns和MaxIdleConns值将带来更好的性能。但你应该注意到效果是递减的,连接池空闲连接太多(连接没有被重用,最终会变坏)实际上会导致性能下降。
3、为了降低上面第2点带来的风险,您可能需要设置一个相对较短的ConnMaxLifetime。但你也不希望它太短,导致连接被杀死或不必要地频繁重建。
4、MaxIdleConns应该总是小于或等于MaxOpenConns。
对于中小型web应用程序,我通常使用以下设置作为起点,然后根据实际吞吐量水平的负载测试结果进行优化。
go语言实现一个简单的简单网关
网关=反向代理+负载均衡+各种策略,技术实现也有多种多样,有基于 nginx 使用 lua 的实现,比如 openresty、kong;也有基于 zuul 的通用网关;还有就是 golang 的网关,比如 tyk。
这篇文章主要是讲如何基于 golang 实现一个简单的网关。
转自: troy.wang/docs/golang/posts/golang-gateway/
整理:go语言钟文文档:
启动两个后端 web 服务(代码)
这里使用命令行工具进行测试
具体代码
直接使用基础库 httputil 提供的NewSingleHostReverseProxy即可,返回的reverseProxy对象实现了serveHttp方法,因此可以直接作为 handler。
具体代码
director中定义回调函数,入参为*http.Request,决定如何构造向后端的请求,比如 host 是否向后传递,是否进行 url 重写,对于 header 的处理,后端 target 的选择等,都可以在这里完成。
director在这里具体做了:
modifyResponse中定义回调函数,入参为*http.Response,用于修改响应的信息,比如响应的 Body,响应的 Header 等信息。
最终依旧是返回一个ReverseProxy,然后将这个对象作为 handler 传入即可。
参考 2.2 中的NewSingleHostReverseProxy,只需要实现一个类似的、支持多 targets 的方法即可,具体实现见后面。
作为一个网关服务,在上面 2.3 的基础上,需要支持必要的负载均衡策略,比如:
随便 random 一个整数作为索引,然后取对应的地址即可,实现比较简单。
具体代码
使用curIndex进行累加计数,一旦超过 rss 数组的长度,则重置。
具体代码
轮询带权重,如果使用计数递减的方式,如果权重是5,1,1那么后端 rs 依次为a,a,a,a,a,b,c,a,a,a,a…,其中 a 后端会瞬间压力过大;参考 nginx 内部的加权轮询,或者应该称之为平滑加权轮询,思路是:
后端真实节点包含三个权重:
操作步骤:
具体代码
一致性 hash 算法,主要是用于分布式 cache 热点/命中问题;这里用于基于某 key 的 hash 值,路由到固定后端,但是只能是基本满足流量绑定,一旦后端目标节点故障,会自动平移到环上最近的那么个节点。
实现:
具体代码
每一种不同的负载均衡算法,只需要实现添加以及获取的接口即可。
然后使用工厂方法,根据传入的参数,决定使用哪种负载均衡策略。
具体代码
作为网关,中间件必不可少,这类包括请求响应的模式,一般称作洋葱模式,每一层都是中间件,一层层进去,然后一层层出来。
中间件的实现一般有两种,一种是使用数组,然后配合 index 计数;一种是链式调用。
具体代码
分享题目:go语言中文guan方文档 go语言文档
URL分享:http://scpingwu.com/article/hpjiee.html