基本都94goxx不能,在继续wwW94goxxcom等录了

&p&ref:&a href=&http://link.zhihu.com/?target=https%3A//blog.golang.org/defer-panic-and-recover& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&blog.golang.org/defer-p&/span&&span class=&invisible&&anic-and-recover&/span&&span class=&ellipsis&&&/span&&/a&&/p&&p&Go语言的流程控制关键字包括:if, for, switch,goto。还有一个go关键字用户运行独立的协程。其中Defer、Panic和Recover的用法如下:&/p&&p&&br&&/p&&h2&Defer&/h2&&p&Defer语句保存一个方法调用到一个调用列表中,这个调用列表中保存的方法将在调用Defer语句的方法返回时执行。Defer经常被用来在方法最后调用执行方法的资源回收。&/p&&p&例如,下面文件复制的例子:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
dst, err := os.Create(dstName)
if err != nil {
written, err = io.Copy(dst, src)
dst.Close()
src.Close()
&/code&&/pre&&/div&&p&上面的代码可以执行,但是有个bug。当调用os.Create发生错误时,方法将直接返回但是source文件没有被正常关闭。当然可以在第二个return语句前调用os.Close,但是如果是其它复杂的情况处理上就不会这么简单。使用Defer可以很简单的解决,上面的代码可以改成:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
defer dst.Close()
return io.Copy(dst, src)
&/code&&/pre&&/div&&p&Defer允许我们在打开文件后,安全的关闭而不用去关心方法中的各种复杂情况。&/p&&p&Defer的行为简单并且可预测,有三种简单规则:&/p&&ol&&li&Defer调用的function的参数的值在调用defer时已经传递。例如下面的代码将打印“0”。&/li&&/ol&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func a() {
defer fmt.Println(i)
&/code&&/pre&&/div&&p&
2. Defer function按照后进先出的规则执行。例如下面的代码打印“3210”。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func b() {
for i := 0; i & 4; i++ {
defer fmt.Print(i)
&/code&&/pre&&/div&&p&
3. Defer function在方法的return之后执行,如果defer function修改了return的值,返回的是defer function修改后的值。例如下面的例子返回2而不是1。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func c() (i int) {
defer func() { i++ }()
&/code&&/pre&&/div&&p&这个功能让我们可以方便的修改一个方法的错误返回值。&/p&&h2&Panic&/h2&&p&panic中断方法内执行的一般流程并且开始执行panicking(可以理解成抛出异常)。当某方法调用panic语句时,这个方法会立即停止后面未执行的代码,但会继续把该方法中所有已经deferred的方法调用执行完,然后该方法返回调用者;调用者会执行和该方法相同的行为直到在当前协程中的所有方法栈都执行完同样的行为并返回,最后程序将崩溃。Panics可以通过简单的调用panic来执行,也可以由runtime error导致,比如“out-of-bounds array accesses”。&/p&&h2&Recover&/h2&&p&recover用来在方法里捕获、处理发送panicking的协程。Recover一般在deferred方法中调用。经过了正常的执行,recover调用将返回nil并且没有其它的影响。如果当前的协程发生panicking,recover调用将捕获panicking抛出的值并且恢复协程正常的执行。&/p&&p&&br&&/p&&p&下面的例子说明如何使用panic和recover:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package main
import &fmt&
func main() {
fmt.Println(&Returned normally from f.&)
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println(&Recovered in f&, r)
fmt.Println(&Calling g.&)
fmt.Println(&Returned normally from g.&)
func g(i int) {
if i & 3 {
fmt.Println(&Panicking!&)
panic(fmt.Sprintf(&%v&, i))
defer fmt.Println(&Defer in g&, i)
fmt.Println(&Printing in g&, i)
&/code&&/pre&&/div&&p&这个方法输出:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
&/code&&/pre&&/div&&p&如果把调用recover的defer方法移除,则最终协程将崩溃,输出结果如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic PC=0x2a9cd8
[stack trace omitted]
&/code&&/pre&&/div&&p&For a real-world example of &b&panic&/b& and &b&recover&/b&, see the &u&&a href=&http://link.zhihu.com/?target=http%3A//golang.org/pkg/encoding/json/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&json package&/a&&/u& from the Go standard library. It decodes JSON-encoded data with a set of recursive functions. When malformed JSON is encountered, the parser calls panic to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'unmarshal' methods of the decodeState type in &u&&a href=&http://link.zhihu.com/?target=http%3A//golang.org/src/pkg/encoding/json/decode.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&decode.go&/a&&/u&).&/p&&p&The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.&/p&&p&Other uses of &b&defer&/b& (beyond the file.Close example given earlier) include releasing a mutex:&/p&&p&mu.Lock()&br&defer mu.Unlock()&/p&&p&printing a footer:&/p&&p&printHeader()&br&defer printFooter()&/p&&p&and more.&/p&&p&In summary, the defer statement (with or without panic and recover) provides an unusual and powerful mechanism for control flow. It can be used to model a number of features implemented by special-purpose structures in other programming languages. Try it out.&/p&&p&&i&By Andrew Gerrand&/i&&/p&
ref:Go语言的流程控制关键字包括:if, for, switch,goto。还有一个go关键字用户运行独立的协程。其中Defer、Panic和Recover的用法如下: DeferDefer语句保存一个方法调用到一个调用列表中,这个调用列表中保存的方法将在调用Defer语…
首先本文并不是针对某网站来的。&p&我是怀有深深的xx来写的。以方便日后。&/p&&p&大哥大姐如果看到小弟的文章不屑一顾的话,到此就可以走了,要不我送送你。&/p&&p&计算机环境使用的vs2015社区版,数据库使用的MSSQL 2012(偷偷的说:使用的是网上的密钥哈。)&/p&&p&说一下简单的原理:&/p&&p&首先模拟浏览获取网页。&/p&&p&其次分析网页的内容。&/p&&p&最后根据自己的需要把数据储存在数据库中。(超级简单吧)&/p&&p&备注一句:如果是分布式爬虫的话,我考虑考虑哈,毕竟我还是很菜的哈。&/p&&p&在我使用的HTTP请求不带COOKIE。加不加都不复杂。&/p&&p&------------编辑------------&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&using(Stream writer = Request.GetRequestStream(){
byte[] data=Encoding.GetEncoding(&UTF-8&).GetBytes(&cookie&);
request.ContentLength = data.L
writer.Write(data,0,data.Length);
&/code&&/pre&&/div&&br&&p&下面代码请求HTTP。&/p&&p&HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(&地址&);&/p&&p&Request.Timeout = 20 * 1000;//请求超时。&/p&&p&Request.AllowAutoRedirect = //网页自动跳转。&/p&&p&Request.UserAgent = &Mozilla/5.0 ( Googlebot/2.1; +&a href=&https://link.zhihu.com/?target=http%3A//www.google.com/bot.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&google.com/bot.html&/span&&span class=&invisible&&&/span&&/a&)&;//伪装成谷歌爬虫。&/p&&p&Request.Method = &GET&; //获取数据的方法。GET .POST&/p&&p&Request.KeepAlive = //保持&/p&&p&HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();&/p&&p&using (StreamReader sReader = new StreamReader(Response.GetResponseStream(), Encoding.UTF8))&/p&&p& {&/p&&p&
String Htmlstring = sReader.ReadToEnd();&/p&&p& }&/p&&p&该HtmlString即获取的网页源代码。&/p&&p&其实网页源代码是真正爬虫难以处理的问题。&/p&&p&各个网站都有不同的网页结构。网页复杂,不规范。导致解析网页成为巨大的问题。&/p&&p&当然爬虫毕竟不是浏览器,但是复杂的爬虫会有浏览器的部分功能。&/p&&p&比如现在流行的动态网页加载。Jquery库来实现动态网页的添加。&/p&&p&那你的爬虫就必须能够解析简单的JS。当然认为解析js过于复杂,那我就不解析了撒(自欺欺人,技术达不到也没有办法)。&/p&&p&下面开始解析HTML网页:&/p&&p&首先我们要知道这个网页对我们哪些内容是有用的,哪些是没有用的。&/p&&p&这里开始我们将使用正则表达式。&/p&&p&用正则表达式来剔除不需要的Html。&/p&&p&//删除\r&/p&&p&Htmlstring = Regex.Replace(Htmlstring, @&\r&, String.Empty, RegexOptions.IgnoreCase);&/p&&p&//删除\n&/p&&p&Htmlstring = Regex.Replace(Htmlstring, @&\n&, String.Empty, RegexOptions.IgnoreCase);&/p&&p&删除\r\n是我在发现不规范的网页源代码中无法正确的匹配正则表达式。&/p&&p&所以以我的判断首先删除他们两个。(这是我认为的重点。)&/p&&p& //删除script&/p&&p&Htmlstring = Regex.Replace(Htmlstring, @&&script[^&]*?&.*?&/script&&, String.Empty, RegexOptions.IgnoreCase);&/p&&p& //删除title&/p&&p&Htmlstring = Regex.Replace(Htmlstring, @&&title[^&]*?&.*?&/title&&, String.Empty, RegexOptions.IgnoreCase);&/p&&p&//删除head&/p&&p&Htmlstring = Regex.Replace(Htmlstring, @&&head[^&]*?&.*?&/head&&, String.Empty, RegexOptions.IgnoreCase);&/p&&p&//删除img&/p&&p&Htmlstring = Regex.Replace(Htmlstring, @&&img(.[^&]*)&&, String.Empty, RegexOptions.IgnoreCase);&/p&&p&//删除video&/p&&p&Htmlstring = Regex.Replace(Htmlstring, @&&video[^&]*?&.*?&/video&&, String.Empty, RegexOptions.IgnoreCase);&/p&&p&我致力于提取网页中的文本。所以图片,视频,标题我一一删除。&/p&&p&脚本,样式。我都删除了。&/p&&p&重点提取网页中的超链接的地址:&/p&&p&string regexString = @&&\s*a\s+[^&]*href\s*=\s*[&&'](?&HREF&[^&&']*)[&&'][^&]*&(?&IHTML&[\s\S]+?)&\s*/\s*a\s*&&;&/p&&p&Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);&/p&&p&MatchCollection matchs = regex.Matches(Htmlstring);&/p&&p&foreach (Match match in matchs)&/p&&p&{&/p&&p&string match_string = match.Groups[&HREF&].Value.ToLower();&/p&&p&}&/p&&p&该代码重点提取网页的中的超链接。&/p&&p&至于提取的超链接的地址,是直接储存至数据库的。&/p&&p&这儿有一个我自己体会:&/p&&p&如果在一个网页中提取几百上千个地址。这个地址数量是庞大的。我们需要把这些储存数据库中。所以我要一次插入上千条数据,但是数据库开销是昂贵的。所以数据库只能打开一次。&/p&&p&这里有个问题。如果数据库有庞大的地址库。这时我们我就需要一条一条在数据库查找是否有重复的地址。这儿注释一个数据库去重的。&/p&&p&using (SqlCommand cmd = new SqlCommand(&Select Top 1 id From &, cnn)) {&/p&&p&using (SqlDataReader dr = cmd.ExecuteReader()){&/p&&p&if (dr.Read()) {&/p&&p&flag =&/p&&p& }else{&/p&&p& flag =&/p&&p&}&/p&&p&}&/p&&p&}&br&&/p&&p&if (flag){&/p&&p&using (SqlCommand cmd = new SqlCommand(&Insert Into &, cnn)){&/p&&p&cmd.ExecuteNonQuery();&/p&&p&}&/p&&p&数据库查询的时候为什么Select Top 1 。因为我们只需要在数据库确认是否有改地址。如果没有Top 1, 查询语句会把整个表遍历一次。这个根本没有必要的。如果有该地址迅速返回。这样有利于提高Select。&/p&&p&我们用正则表达式提取的地址。也未大不一样哈。有绝地地址的。有相对地址的(相对地址不规范的。)还有js代码的。会看的你眼花缭乱。还有地址链接中被Html解析了的。&/p&&p&match = Regex.Replace(match, &&&, &&&, RegexOptions.IgnoreCase);&/p&&p&地址含有&明显就含有Html元素。故此,需要替换正常的参数连接符。&/p&&p&地址含有javascript:void(0);明显就是就js代码过滤掉。&/p&&p&地址中不规范的那就多了哈:&/p&&p&比如当前访问的域名的&a href=&https://link.zhihu.com/?target=http%3A//nearbyshare.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&鬼脸&/a&(我的网站,嘿嘿)&/p&&p&里面的相对地址以“/”开头,但是我们的访问的域名&a href=&https://link.zhihu.com/?target=http%3A//www.nearbyshare.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&鬼脸&/a&却是这个。这样的话我们就要不能把当前域名和相对地址直接连接起来。而是需要把相对地址的“/”删除掉才能连接起来。&/p&&p&还有绝对地址的判断我们也需要采用正则表达式来:&/p&&p&string regex = @&http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?&;(绝对地址正则,网上找的)&br&&/p&&p&至此我认为简单的对网页源代码的提取超链接地址差不多了。&/p&&p&对网页重要的文本内容的提取哈:&/p&&p&对网页图片的提取,对网页视频的提取。都类似,也可以同时提取。&/p&&p&下面对于当前网页的获取:&/p&&p&在数据库中提取当前网页的网页来检索。我想有深度算法和广度算法。&/p&&p&深度爬取网页&/p&&p&广度爬取网页&/p&&p&偶认为应该两则结合才更好。&/p&&p&不展开了。因为展不开了哈。哈哈哈哈哈哈哈哈。。。。。。&/p&&br&&p&————&/p&&p&其实该代码属于单线程。&/p&&p&更新多线程代码还木有写。&/p&&p&但原理经典的生产者与消费者关系。&/p&&p&一个线程不停的下载。把下载的数据推入一个先入先出的队列。&/p&&p&另一个线程不停从队列中取出数据进行分析。&/p&&p&当然下载线程也可以多个。这时,下载线程对队列进行写的时候保持不被其他写线程使用。保持线程互斥。&/p&&p&好了暂时写到这里,好像还没有说的东西还多哦。不知道有没有用,反正我用它正在爬取网页。&/p&&br&&p&我失业了,有没有人介绍一个工作呢??&/p&&p&要不来浏览一下我的网站&a href=&https://link.zhihu.com/?target=http%3A//www.nearbyshare.com& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&鬼脸&/a&哈。&/p&
首先本文并不是针对某网站来的。我是怀有深深的xx来写的。以方便日后。大哥大姐如果看到小弟的文章不屑一顾的话,到此就可以走了,要不我送送你。计算机环境使用的vs2015社区版,数据库使用的MSSQL 2012(偷偷的说:使用的是网上的密钥哈。)说一下简单的原理…
&p&你要这么用&/p&&p&time.ParseInLocation(& 15:04&, time_string, time.Local),time默认用utc和我们差8个小时&/p&
你要这么用time.ParseInLocation(" 15:04", time_string, time.Local),time默认用utc和我们差8个小时
&p&学习任何一门语言,都要学习好基础,把基础打牢,那些框架对你来说都是工具,你自己的基础好,懂得了他们的原理,自己就可以创造更优秀的框架。&/p&&p&基础推荐官方文档,没有什么比这个更清晰了。官方文档可以看这个中文的,比较快一些 &a href=&//link.zhihu.com/?target=https%3A//go-zh.org/doc/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&go-zh.org/doc/&/span&&span class=&invisible&&&/span&&/a&&/p&&p&其次参考这个Go指南,练习一遍 &a href=&//link.zhihu.com/?target=https%3A//tour.go-zh.org/welcome/1& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&tour.go-zh.org/welcome/&/span&&span class=&invisible&&1&/span&&span class=&ellipsis&&&/span&&/a&&/p&&p&现在对Go语言应该有了一个全面的认识,然后你再结合Go语言圣经这本书,深入理解Go的基础。&/p&&p&Go语言圣经中文版 &a href=&//link.zhihu.com/?target=https%3A//www.gitbook.com/book/yar999/gopl-zh/details& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&gitbook.com/book/yar999&/span&&span class=&invisible&&/gopl-zh/details&/span&&span class=&ellipsis&&&/span&&/a&&/p&&p&然后通过《Go语言实战》这本书,In Action系列都是比较经典的,看这本书的实习可以参考我《Go语言实战》这本书的读书笔记,一共近30篇文章,15万字,非常全面,书里没有的我这里也讲到了很多。这里列其中几篇:&/p&&ol&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//install-golang.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言开发环境搭建详解&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-in-action-go-package.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言实战笔记(一)| Go包管理&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-in-action-go-slice.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言实战笔记(五)| Go 切片&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-in-action-go-interface.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言实战笔记(九)| Go 接口&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-in-action-go-concurrent-resource.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言实战笔记(十三)| Go 并发资源竞争&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-in-action-go-context.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言实战笔记(二十)| Go Context&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-in-action-go-reflect.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言实战笔记(二十四)| Go 反射&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-in-action-unsafe-memory-layout.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言实战笔记(二十六)| Go unsafe 包之内存布局&/a&&/li&&/ol&&p&最终再通过一些别的第三方库源代码和实践,就可以完全掌握了。我最近也在分析一些经典库,可以保持关注:&/p&&ol&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-classic-libs-start.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言经典库使用分析(一)| 开篇&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-classic-libs-gorilla-context.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言经典库使用分析(二)| Gorilla Context&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-classic-libs-gorilla-handlers-guide.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言经典库使用分析(三)| Gorilla Handlers 详细介绍&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-classic-libs-gorilla-handlers-sources.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言经典库使用分析(四)| Gorilla Handlers 源代码实现分析&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-classic-libs-negroni-one.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言经典库使用分析(五)| Negroni 中间件(一)&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-classic-libs-negroni-two.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言经典库使用分析(六)| Negroni 中间件(二)&/a&&/li&&/ol&&p&&b&基础进阶&/b&:&/p&&ol&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//golang-http-proxy.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&一个简单的Golang实现的HTTP Proxy&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//golang-socket5-proxy.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&一个简单的Golang实现的Socket5 Proxy&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//golang-hot-project-in-github.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GitHub上优秀的Go开源项目&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//from-java-to-golang.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&从Java到Golang快速入门&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//golang-function-interface.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Golang必备技巧:接口型函数&/a& &/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-spider-for_lagou.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言爬虫抓取拉勾职位--提升找工作成功概率&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-1-9-type-alias.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言 | Go 1.9 新特性 Type Alias详解&/a& &/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-qrcode.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言生成二维码是如此简单&/a& &/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-new-vs-make.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言中new和make的区别&/a& &/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-auto-choice-json-libs.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言中自动选择json解析库&/a& &/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//golang-goquery-examples-selector.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&golang goquery selector(选择器) 示例大全&/a& &/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//go-regexp-extract-text.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言中使用正则提取匹配的字符串&/a& &/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.flysnow.org//golang-function-parameters-passed-by-value.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go语言参数传递是传值还是传引用&/a&&/li&&/ol&&p&更多更详细更新的可以关注我上面的【公】【众】【号】【flysnow_org】。&/p&&p&&br&&/p&&p&最后,希望这个可以帮助你。。&/p&
学习任何一门语言,都要学习好基础,把基础打牢,那些框架对你来说都是工具,你自己的基础好,懂得了他们的原理,自己就可以创造更优秀的框架。基础推荐官方文档,没有什么比这个更清晰了。官方文档可以看这个中文的,比较快一些 其次…
&figure&&img src=&https://pic7.zhimg.com/v2-0b9e2e5a481c253fe23e89_b.jpg& data-rawwidth=&600& data-rawheight=&331& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic7.zhimg.com/v2-0b9e2e5a481c253fe23e89_r.jpg&&&/figure&&ul&&li&原文地址:&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/%23Parallel& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Concurrent programming&/a&&/li&&li&原文作者:&a href=&https://link.zhihu.com/?target=https%3A//plus.google.com/%2BStefanNilsson& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&StefanNilsson&/a&&/li&&li&译文出自:&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&掘金翻译计划&/a&&/li&&li&本文永久链接:&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner/blob/master/TODO/concurrent-programming.md& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com/xitu/gold-mi&/span&&span class=&invisible&&ner/blob/master/TODO/concurrent-programming.md&/span&&span class=&ellipsis&&&/span&&/a&&/li&&li&译者:&a href=&https://link.zhihu.com/?target=http%3A//github.com/kobehaha& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kobehaha&/a&&/li&&/ul&&p&&br&&/p&&h2&并发编程&/h2&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-4cfa01f2b0a7d0afea07922_b.jpg& data-caption=&& data-rawwidth=&640& data-rawheight=&426& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic4.zhimg.com/v2-4cfa01f2b0a7d0afea07922_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Thread& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&1. 多线程执行&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Chan& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&2. Channels&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Sync& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&3. 同步&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Dead& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&4. 死锁&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Race& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&5. 数据竞争&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Lock& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&6. 互斥锁&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Race2& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&7. 检测数据竞争&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Select& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&8. Select标识符&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Match& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&9. 最基本的并发实例&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=chrome-extension%3A//pghodfjepegmciihfhdipmimghiakcjf/sandbox/preview.html%23Parallel& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&10. 并行计算&/a&&/li&&/ul&&p&这篇文章将会以&a href=&https://link.zhihu.com/?target=https%3A//golang.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go&/a&语言举例介绍并发编程,包括以下内容&/p&&ul&&li&线程的并发执行(goroutines)&/li&&li&基本的同步技术(channel和锁)&/li&&li&Go中的基本并发模式&/li&&li&死锁和数据竞争&/li&&li&并行计算&/li&&/ul&&p&开始之前,你需要去了解怎样写最基本的 Go 程序。 如果你已经对 C/C++,Java 或者Python比较熟悉,&a href=&https://link.zhihu.com/?target=https%3A//tour.golang.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&A tour of go&/a&将会给你一些帮助。你也可以看一下&a href=&https://link.zhihu.com/?target=https%3A//code.google.com/p/go-wiki/wiki/GoForCPPProgrammers& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go for C++ programmers&/a& 或者&a href=&https://link.zhihu.com/?target=http%3A//www.nada.kth.se/%7Esnilsson/go_for_java_programmers/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go for Java programmers&/a&。&/p&&h2&1.多线程执行&/h2&&p&&a href=&https://link.zhihu.com/?target=https%3A//golang.org/ref/spec%23Go_statements& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&goroutine&/a& 是 go 的一种调度机制。 Go 使用 go 进行声明,以 goroutine 调度机制开启一个新的执行线程。它会在新创建的 goroutine 执行程序。在单个程序中,所有goroutines都是共享相同的地址空间。&/p&&p&相比于分配栈空间,goroutine 更加轻量,花销更小。栈空间初始化很小,需要通过申请和释放堆空间来扩展内存。Goroutines 内部是被复用在多个操作系统线程上。如果一个goroutine阻塞了一个操作系统线程,比如正在等待输入,此时,这个线程中的其他 goroutine 为了保证继续运行,将会迁移到其他线程中,而你不需要去关心这些细节。&/p&&p&下面的程序将会打印 &Hello from main goroutine&. 是否打印&Hello from another goroutine&,取决于两个goroutines谁先完成.&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func main() {
go fmt.Println(&Hello from another goroutine&)
fmt.Println(&Hello from main goroutine&)
// 程序执行到这,所有活着的goroutines都会被杀掉
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/goroutine1.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&goroutine1.go&/a&&/p&&p&下一段程序 &Hello from main goroutine& 和 &Hello from another goroutine& 可能会以任何顺序打印。但有一种可能性是第二个goroutine运行的非常慢,以至于到程序结束之前都不会打印。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func main() {
go fmt.Println(&Hello from another goroutine&)
fmt.Println(&Hello from main goroutine&)
time.Sleep(time.Second) // 为其他goroutine完成等1秒钟
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/goroutine2.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&goroutine2.go&/a&&/p&&p&这有一个更实际的例子,我们定义一个使用并发来推迟事件的函数。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 在指定时间过期后,文本会被打印到标准输出
// 这无论如何都不会被阻塞
func Publish(text string, delay time.Duration) {
go func() {
time.Sleep(delay)
fmt.Println(&BREAKING NEWS:&, text)
}() // 注意括号。我们必须调用匿名函数
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/publish1.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&publish1.go&/a&&/p&&p&你可能用下面的方式调用 Publish 函数&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func main() {
Publish(&A goroutine starts a new thread of execution.&, 5*time.Second)
fmt.Println(&Let’s hope the news will published before I leave.&)
// 等待消息被发布
time.Sleep(10 * time.Second)
fmt.Println(&Ten seconds later: I’m leaving now.&)
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/publish1.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&publish1.go&/a&&/p&&p&该程序很有可能按以下顺序打印三行,每行输出会间隔五秒钟。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&$ go run publish1.go
Let’s hope the news will published before I leave.
BREAKING NEWS: A goroutine starts a new thread of execution.
Ten seconds later: I’m leaving now.
&/code&&/pre&&/div&&p&一般来说,我们不可能让线程休眠去等待对方。在下一节中, 我们将会介绍 Go 的一种同步机制, &b&channels&/b&。然后演示如何使用channel来让一个 goruntine 等待另外的 goruntine。&/p&&h2&2. Channels&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-38abd2de5_b.jpg& data-caption=&& data-rawwidth=&580& data-rawheight=&304& class=&origin_image zh-lightbox-thumb& width=&580& data-original=&https://pic1.zhimg.com/v2-38abd2de5_r.jpg&&&/figure&&p&&br&&/p&&p&寿司输送带&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//golang.org/ref/spec%23Channel_types& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&channel&/a& 是一种 Go 语言结构,它通过传递特定元素类型的值来为两个 goroutines 提供同步执行和交流数据的机制 。 &- 标识符表示了channel的传输方向,接收或者发送。如果没有指定方向。那么 channel 就是双向的。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&chan Sushi
// 能被用于接收和发送 Sushi 类型的值
chan&- float64
// 只能被用于发送 float64 类型的值
&-chan int
// 只能被用于接收 int 类型的值
&/code&&/pre&&/div&&p&Channels 是一种被 make 分配的引用类型&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&ic := make(chan int)
// 不带缓存的
int channel
wc := make(chan *Work, 10)
// 带缓冲工作的 channel
&/code&&/pre&&/div&&p&通过 channel 发送值,可使用 &- 作为二元运算符。通过 channel 接收值,可使用它作为一元运算符。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&ic &- 3
// 向channel中发送3
work := &-wc
// 从channel中接收指针到work
&/code&&/pre&&/div&&p&如果 channel 是无缓冲的,发送者会一直阻塞直到有接收者从中接收值。如果是带缓冲的,只有当值被拷贝到缓冲区且缓冲区已满时,发送者才会阻塞直到有接收者从中接收。接收者会一直阻塞直到 channel 中有值可被接收。&/p&&h2&关闭&/h2&&p&&a href=&https://link.zhihu.com/?target=https%3A//golang.org/ref/spec%23Close& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&close&/a& 的作用是保证不能再向 channel 中发送值。 channel 被关闭后,仍然是可以从中接收值的。接收操作会获得零值而不会阻塞。多值接收操作会额外返回一个布尔值,表示该值是否被发送的。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&ch := make(chan string)
go func() {
ch &- &Hello!&
fmt.Println(&-ch)
// 打印 &Hello!&
fmt.Println(&-ch)
// 不阻塞的打印空值 &&
fmt.Println(&-ch)
// 再一次打印 &&
v, ok := &-ch
// v 的值是 && , ok 的值是 false
&/code&&/pre&&/div&&p&伴有 range 分句的 for 语句会连续读取通过 channel 发送的值,直到 channel 被关闭&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func main() {
var ch &-chan Sushi = Producer()
for s := range ch {
fmt.Println(&Consumed&, s)
func Producer() &-chan Sushi {
ch := make(chan Sushi)
go func() {
ch &- Sushi(&海老握り&)
// Ebi nigiri
ch &- Sushi(&鮪とろ握り&) // Toro nigiri
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/sushi.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&sushi.go&/a&&/p&&h2&3.同步&/h2&&p&下一个例子中,Publish 函数返回一个channel,它会把发送的文本当做消息广播出去。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 指定时间过期后函数Publish将会打印文本到标准输出.
// 当文本被发布channel将会被关闭.
func Publish(text string, delay time.Duration) (wait &-chan struct{}) {
ch := make(chan struct{})
go func() {
time.Sleep(delay)
fmt.Println(&BREAKING NEWS:&, text)
close(ch) // broadcast – a closed channel sends a zero value forever
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/publish2.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&publish2.go&/a&&/p&&p&注意我们使用一个空结构的 channel : struct{}。 这表明该 channel 仅仅用于信号,而不是传递数据。&/p&&p&你可能会这样使用该函数&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func main() {
wait := Publish(&Channels let goroutines communicate.&, 5*time.Second)
fmt.Println(&Waiting for the news...&)
fmt.Println(&The news is out, time to leave.&)
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/publish2.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&publish2.go&/a&&/p&&p&程序将按给出的顺序打印下列三行信息。在信息发送后,最后一行会立刻出现&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&$ go run publish2.go
Waiting for the news...
BREAKING NEWS: Channels let goroutines communicate.
The news is out, time to leave.
&/code&&/pre&&/div&&h2&4.死锁&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-b46d2c9de6e6746fdf35ccff_b.jpg& data-caption=&& data-rawwidth=&640& data-rawheight=&342& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic1.zhimg.com/v2-b46d2c9de6e6746fdf35ccff_r.jpg&&&/figure&&p&&br&&/p&&p&让我们去介绍 Publish 函数中的一个bug。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func Publish(text string, delay time.Duration) (wait &-chan struct{}) {
ch := make(chan struct{})
go func() {
time.Sleep(delay)
fmt.Println(&BREAKING NEWS:&, text)
**//close(ch)**
&/code&&/pre&&/div&&p&这时由 Publish 函数开启的 goroutine 打印重要信息然后退出,留下主 goroutine 继续等待。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func main() {
wait := Publish(&Channels let goroutines communicate.&, 5*time.Second)
fmt.Println(&Waiting for the news...&)
**&-wait**
fmt.Println(&The news is out, time to leave.&)
&/code&&/pre&&/div&&p&在某些情况下,程序将不会有任何进展,这种情况被称为死锁。&/p&&blockquote&&i&deadlock&/i& 是线程之间相互等待而都不能继续执行的一种情况&/blockquote&&p&在运行时,Go 对于运行时死锁检测具有良好支持。但在某种情况下goroutine无法取得任何进展,这时Go程序会提供一个详细的错误信息. 下面就是我们崩溃程序的日志:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Waiting for the news...
BREAKING NEWS: Channels let goroutines communicate.
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
.../goroutineStop.go:11 +0xf6
goroutine 2 [syscall]:
created by runtime.main
.../go/src/pkg/runtime/proc.c:225
goroutine 4 [timer goroutine (idle)]:
created by addtimer
.../go/src/pkg/runtime/ztime_linux_amd64.c:73
&/code&&/pre&&/div&&p&多数情况下下,在 Go 程序中很容易搞清楚是什么导致了死锁。接着就是如何去修复它了。&/p&&h2&5. 数据竞争&/h2&&p&死锁可能听起来很糟糕, 但是真正给并发编程带来灾难的是数据竞争。它们相当常见,而且难于调试。&/p&&blockquote&一个 &i&数据竞争&/i& 发生在当两个线程并发访问相同的变量,同时最少有一个访问是在写.&/blockquote&&p&数据竞争是没有规律的。举个例子,打印数字1,尝试找出它是如何发生的 — 一个可能的解释是在代码之后.&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func race() {
wait := make(chan struct{})
go func() {
**n++** // 一次操作:读,增长,写
close(wait)
**n++** // 另一个冲突访问
fmt.Println(n) // 输出: 不确定
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/datarace.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&datarace.go&/a&&/p&&p&两个goroutines, g1 和 g2, 在竞争过程中,我们无法知道他们执行的顺序.下面只是许多可能的结果性的一种.&/p&&ul&&li&g1 从n变量中读取值0&/li&&li&g2 从n变量中读取值0&/li&&li&g1 增加它的值从0变为1&/li&&li&g1 把它的值把1赋值给n&/li&&li&g2 增加它的值从0到1&/li&&li&g2 把它的值把1赋值给n&/li&&li&这段程序将会打印n的值,它的值为1&/li&&/ul&&p&&数据竞争” 的称呼多少有些误导,不仅仅是他的执行顺序无法被设定,而且也无法保证接下来会发生的情况。编译器和硬件时常会为了更好的性能而调整代码的顺序。如果你仔细观察一个正在运行的线程,那么你才可能会看到更多细节。&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-ec5db2ed14db3d8671efdc_b.jpg& data-caption=&& data-rawwidth=&640& data-rawheight=&480& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic3.zhimg.com/v2-ec5db2ed14db3d8671efdc_r.jpg&&&/figure&&p&&br&&/p&&p&避免数据竞争的唯一方式是同步操作在线程间所有共享的可变数据。存在几种方式,在Go中,可能最多使用 channel 或者 lock。较底层的操作可使用 &a href=&https://link.zhihu.com/?target=https%3A//golang.org/pkg/sync/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&sync&/a& and &a href=&https://link.zhihu.com/?target=https%3A//golang.org/pkg/sync/atomic/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&sync/atomic&/a& 包,这里不再讨论。&/p&&p&在Go中,处理并发数据访问的首选方式是使用一个 channel,它将数据从一个goroutine传递到另一个goroutine。有一句经典的话:&不要通过共享内存来传递数据;而要通过传递数据来共享内存&。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func sharingIsCaring() {
ch := make(chan int)
go func() {
n := 0 // 局部变量只能对当前 goroutine 可见
ch &- n // 数据通过 goroutine 传递
// ...从另外一个 goroutine 中安全接受
fmt.Println(n) // 输出: 2
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/datarace.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&datarace.go&/a&&/p&&p&在这份代码中 channel 充当了双重角色。它作为一个同步点,在不同 goroutine 中传递数据。发送的 goroutine 将会等待其它的 goroutine 去接收数据,而接收的 goroutine 将会等待其他的 goroutine 去发送数据。&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//golang.org/ref/mem& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Go内存模型&/a& - 当一个 goroutine 在读一个变量,另外一个goroutine在写相同的变量,这个过程实际上是非常复杂的,但是只要你用 channel 在不同goroutines中共享数据,那么这个操作就是安全的。&/p&&h2&6. 互斥锁&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-3615deb2b47d1e9396e1_b.jpg& data-caption=&& data-rawwidth=&640& data-rawheight=&480& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic1.zhimg.com/v2-3615deb2b47d1e9396e1_r.jpg&&&/figure&&p&&br&&/p&&p&有时通过直接锁定来同步数据比使用 channel 更加方便。为此,Go 标准库提供了互斥锁&a href=&https://link.zhihu.com/?target=https%3A//golang.org/pkg/sync/%23Mutex& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&sync.Mutex&/a&。&/p&&p&要让这种类型的锁正确工作,所有对于共享数据的操作(包括读和写)必须在一个 goroutine 持有该锁时进行。这一点至关重要,goroutine 的一次错误就足以破坏程序和导致数据竞争。&/p&&p&因此你需要为API去设计一种定制化的数据结构,并且确保所有同步操作都在内部执行。在这个例子中,我们构建了一种安全易用的并发数据结构,AtomicInt,它存储了单个整型,任何goroutines 都能安全的通过 Add 和 Value 方法访问数字。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// AtomicInt 是一种持有int类型的支持并发的数据结构。
// 它的初始化值为0.
type AtomicInt struct {
mu sync.Mutex // 同一时间只能有一个 goroutine 持有锁。
// Add adds n to the AtomicInt as a single atomic operation.
// 原子性的将n增加到AtomicInt中
func (a *AtomicInt) Add(n int) {
a.mu.Lock() // 等待锁被释放然后获取。
a.mu.Unlock() // 释放锁。
// 返回a的值.
func (a *AtomicInt) Value() int {
a.mu.Lock()
a.mu.Unlock()
func lockItUp() {
wait := make(chan struct{})
var n AtomicInt
go func() {
n.Add(1) // one access
close(wait)
n.Add(1) // 另一个并发访问
fmt.Println(n.Value()) // Output: 2
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/datarace.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&datarace.go&/a&&/p&&h2&7. 检测数据竞争&/h2&&p&竞争有时候难以检测。当我执行这段存在数据竞争的程序,它打印55555。再试一次,可能会得到不同的结果。 &a href=&https://link.zhihu.com/?target=https%3A//golang.org/pkg/sync/%23WaitGroup& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&sync.WaitGroup&/a&是go标准库的一部分;它等待一系列 goroutines 执行结束。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func race() {
var wg sync.WaitGroup
for i := 0; i & 5; **i++** {
go func() {
**fmt.Print(i)** // 局部变量i被6个goroutine共享
wg.Wait() // 等待5个goroutine执行结束
fmt.Println()
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/raceClosure.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&raceClosure.go&/a&&/p&&p&对于输出 55555 较为合理的解释是执行 i++ 操作的 goroutine 在其他 goroutines 打印之前就已经执行了5次。事实上,更新后的 i 对于其他 goroutines 可见是随机的。&/p&&p&一个非常简单的解决办法是通过使用本地变量作为参数的方式去启动另外的goroutine。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func correct() {
var wg sync.WaitGroup
for i := 0; i & 5; i++ {
go func(n int) { // 局部变量。
fmt.Print(n)
fmt.Println()
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/raceClosure.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&raceClosure.go&/a&&/p&&p&这段代码是正确的,他打印了期望的结果,24031。回想一下,在不同 goroutines 中,程序的执行顺序是乱序的。&/p&&p&我们仍然可以使用闭包去避免数据竞争。但是我们需要注意在每个 goroutine 中需要有不同的变量。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func alsoCorrect() {
var wg sync.WaitGroup
for i := 0; i & 5; i++ {
n := i // 为每个闭包创建单独的变量
go func() {
fmt.Print(n)
fmt.Println()
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/raceClosure.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&raceClosure.go&/a&&/p&&h2&7. 自动竞争检测&/h2&&p&总的来说.我们不可能自动的发现所有的数据竞争。但是 Go(从1.1版本开始) 提供了一个强大的数据竞争检测器 &a href=&https://link.zhihu.com/?target=http%3A//tip.golang.org/doc/articles/race_detector.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&data race detector&/a&。&/p&&p&这个工具使用下来非常简单: 仅仅增加 -race 到 go 命令后。运行上述程序将会自动检查并且打印出下面的输出信息。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&$ go run -race raceClosure.go
Data race:
==================
WARNING: DATA RACE
Read at 0x00c by goroutine 6:
main.race.func1()
../raceClosure.go:22 +0x3f
Previous write at 0x00c by main goroutine:
main.race()
../raceClosure.go:20 +0x1bd
main.main()
../raceClosure.go:10 +0x2f
Goroutine 6 (running) created at:
main.race()
../raceClosure.go:24 +0x193
main.main()
../raceClosure.go:10 +0x2f
==================
Also correct:
Found 1 data race(s)
exit status 66
&/code&&/pre&&/div&&p&这个工具发现在程序20行存在数据竞争,一个goroutine向某个变量写值,而22行存在另外一个 goroutine 在不同步的读取这个变量的值。&/p&&p&注意这个工具只能找到实际执行时发生的数据竞争。&/p&&h2&8. Select 语句&/h2&&p&在 Go 并发编程中,最后讲的一个是 &a href=&https://link.zhihu.com/?target=https%3A//golang.org/ref/spec%23Select_statements& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&select&/a& 语句。它会挑选出一系列通信操作中能够执行的操作。如果任意的通信操作都可执行,则会随机挑选一个并执行相关的语句。否则,如果也没有默认执行语句的话,则会阻塞直到其中的任意一个通信操作能够执行。&/p&&p&这有一个例子,显示了如何用 select 去随机生成数字.&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// RandomBits 返回产生随机位数的channel
func RandomBits() &-chan int {
ch := make(chan int)
go func() {
case ch &- 0: // 没有相关操作语句
case ch &- 1:
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/randBits.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&randBits.go&/a&&/p&&p&更简单,这里 select 被用于设置超时。这段代码只能打印 news 或者 time-out 消息,这取决于两个接收语句中谁可以执行.&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&select {
case news := &-NewsAgency:
fmt.Println(news)
case &-time.After(time.Minute):
fmt.Println(&Time out: no news in one minute.&)
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//golang.org/pkg/time/%23After& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&time.After&/a&是 go 标准库的一部分;他等待特定时间过去,然后将当前时间发送到返回的 channel.&/p&&h2&9. 最基本的并发实例&/h2&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-c0de6fe5eba_b.jpg& data-caption=&& data-rawwidth=&320& data-rawheight=&310& class=&content_image& width=&320&&&/figure&&p&&br&&/p&&p&多花点时间仔细理解这个例子。当你完全理解它,你将会彻底的理解 Go 内部的并发工作机制。&/p&&p&程序演示了单个 channel 同时发送和接受多个 goroutines 的数据。它也展示了 select 语句如何从多个通信操作中选择执行。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func main() {
people := []string{&Anna&, &Bob&, &Cody&, &Dave&, &Eva&}
match := make(chan string, 1) // 给未匹配的元素预留空间
wg := new(sync.WaitGroup)
for _, name := range people {
go Seek(name, match, wg)
case name := &-match:
fmt.Printf(&No one received %s’s message.\n&, name)
// 没有待处理的发送操作.
// 寻求发送或接收匹配上名称名称的通道,并在完成后通知等待组.
func Seek(name string, match chan string, wg *sync.WaitGroup) {
case peer := &-match:
fmt.Printf(&%s received a message from %s.\n&, name, peer)
case match &- name:
// 等待其他人接受消息.
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/matching.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&matching.go&/a&&/p&&p&实例输出:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&$ go run matching.go
Anna received a message from Eva.
Cody received a message from Bob.
No one received Dave’s message.
&/code&&/pre&&/div&&h2&10. 并行计算&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-e3b57b2945ffbc1f9474e16_b.jpg& data-caption=&& data-rawwidth=&333& data-rawheight=&405& class=&content_image& width=&333&&&/figure&&p&&br&&/p&&p&具有并发特性应用会将一个大的计算划分为小的计算单元,每个计算单元都会单独的工作。&/p&&p&多 CPU 上的分布式计算不仅仅是一门科学,更是一门艺术。&/p&&ul&&li&每个计算单元执行时间大约在100us至1ms之间.如果这些单元太小,那么分配问题和管理子模块的开销可能会增大。如果这些单元太大,整个的计算体系可能会被一个小的耗时操作阻塞。很多因素都会影响计算速度,比如调度,程序终端,内存布局(注意工作单元的个数和 CPU 的个数无关)。&/li&&li&尽量减少数据共享的量。并发写入是非常消耗性能的,特别是多个 goroutines 在不同CPU上执行时。共享数据读操作对性能影响不是很大。&/li&&li&数据的合理组织是一种高效的方式。如果数据保存在缓存中,数据的加载和存储的速度将会大大加快。再次强调,这对写操作来说是非常重要的。&/li&&/ul&&p&下面的例子将会显示如何将多个耗时计算分配到多个可用的 CPU 上。这就是我们想要优化的代码。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&type Vector []float64
// Convolve computes w = u * v, where w[k] = Σ u[i]*v[j], i + j = k.
// Precondition: len(u) & 0, len(v) & 0.
func Convolve(u, v Vector) Vector {
n := len(u) + len(v) - 1
w := make(Vector, n)
for k := 0; k & k++ {
w[k] = mul(u, v, k)
// mul returns Σ u[i]*v[j], i + j = k.
func mul(u, v Vector, k int) float64 {
var res float64
n := min(k+1, len(u))
j := min(k, len(v)-1)
for i := k - i & i, j = i+1, j-1 {
res += u[i] * v[j]
return res
&/code&&/pre&&/div&&p&这个想法很简单:识别适合大小的工作单元,然后在单独的 goroutine 中运行每个工作单元. 这就是 Convolve 的并发版本.&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func Convolve(u, v Vector) Vector {
n := len(u) + len(v) - 1
w := make(Vector, n)
// 将w划分为多个将会计算100us-1ms时间计算的工作单元
size := max(1, 1000000/n)
var wg sync.WaitGroup
for i, j := 0, i & i, j = j, j+size {
if j & n {
// goroutines只为读共享内存.
go func(i, j int) {
for k := k & k++ {
w[k] = mul(u, v, k)
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.nada.kth.se/%7Esnilsson/concurrency/src/convolution.go& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&convolution.go&/a&&/p&&p&当定义好计算单元,通常最好将调度留给程序执行和操作系统。然而,在 Go1.*版本中,你需要指定 goroutines 的个数。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func init() {
numcpu := runtime.NumCPU()
runtime.GOMAXPROCS(numcpu) // 尽量使用所有可用的 CPU
&/code&&/pre&&/div&&p&&a href=&https://link.zhihu.com/?target=https%3A//plus.google.com/%2BStefanNilsson/about%3Frel%3Dauthor& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Stefan Nilsson&/a&&/p&&hr&&blockquote&&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&掘金翻译计划&/a& 是一个翻译优质互联网技术文章的社区,文章来源为 &a href=&https://link.zhihu.com/?target=https%3A//juejin.im/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&掘金&/a& 上的英文分享文章。内容覆盖 &a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner%23android& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android&/a&、&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner%23ios& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&iOS&/a&、&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner%23react& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&React&/a&、&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner%23%25E5%E7%25AB%25AF& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&前端&/a&、&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner%23%25E5%E7%25AB%25AF& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&后端&/a&、&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner%23%25E4%25BA%25A7%25E5%& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&产品&/a&、&a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner%23%25E8%25AE%25BE%25E8%25AE%25A1& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&设计&/a& 等领域,想要查看更多优质译文请持续关注 &a href=&https://link.zhihu.com/?target=https%3A//github.com/xitu/gold-miner& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&掘金翻译计划&/a&、&a href=&https://link.zhihu.com/?target=http%3A//weibo.com/juejinfanyi& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&官方微博&/a&、&a href=&https://zhuanlan.zhihu.com/juejinfanyi& class=&internal&&知乎专栏&/a&。&/blockquote&
原文地址:原文作者:译文出自:本文永久链接:译者: 并发编程
&p&本文尝试用最清晰的描述回答以下四个问题,当我们需要处理错误的时候:&/p&&ol&&li&什么时候选择 error&/li&&li&什么时候选择 log&/li&&li&什么时候选择 metrics&/li&&li&什么时候选择 panic&/li&&/ol&&p&以及为什么做这样的选择。&/p&&h2&用户分析&/h2&&p&处理错误的关键是,谁来处理?&/p&&ul&&li&对于你的用户而言:他们可能是一段代码,把你的代码做为lib来使用。他们可能是最终的用户,使用你的代码做为日常工具。&/li&&li&对于你的同事(比如说运维)而言:他们负责喊你来修bug。&/li&&li&对于你而言:你负责这段代码的正确运行,以及修复bug。&/li&&/ul&&p&对于这三类人群,他们的诉求是完全不同的。错误处理的艺术就是同时让这三类人群满意。所以我们需要先对每个人群的诉求进行深入分析&/p&&h2&你的用户&/h2&&p&他们的目的是使用你的代码。当你的代码无法满足需求的时候,你得清晰地告诉他们。hey,这个时候你无法使用我了,因为我搞砸了。比如说&/p&&div class=&highlight&&&pre&&code class=&language-go&&&span&&/span&&span class=&kd&&func&/span& &span class=&nx&&ValidatePassport&/span&&span class=&p&&(&/span&&span class=&nx&&passport&/span& &span class=&kt&&string&/span&&span class=&p&&)&/span& &span class=&p&&(&/span&&span class=&nx&&UserId&/span&&span class=&p&&,&/span& &span class=&kt&&error&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&这里返回的 error 就是用来给你的用户传递这个信息的。但是做为用户是需要推动问题解决的,你不能仅仅告诉他们我搞砸了,比如&/p&&div class=&highlight&&&pre&&code class=&language-go&&&span&&/span&&span class=&kd&&func&/span& &span class=&nx&&ValidatePassport&/span&&span class=&p&&(&/span&&span class=&nx&&passport&/span& &span class=&kt&&string&/span&&span class=&p&&)&/span& &span class=&p&&(&/span&&span class=&nx&&UserId&/span&&span class=&p&&,&/span& &span class=&kt&&bool&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&如果你仅仅返回了 true/false,用户是无法知道这是一个问题。也不知道该怎么办。比如是否应该给你的公司打电话。用户希望得到的信息是这么几种&/p&&ul&&li&当用户是一个真人的时候,他们只会把错误分成两类:&/li&&ul&&li&噢,网络错误。那不用管了。等会再回来试试吧。&/li&&li&未知错误。呀,这个不是重试能解决的。我得找人。这个错误消息得足够让我找到人,并且他能接下来跟进。比如得有个订单号什么的东西。&/li&&/ul&&li&当用户是一段程序得时候:我可以对指定的几种错误,进行针对性的处理。比如你告诉我文件不存在,那我可以创建一个新文件。&/li&&/ul&&p&所以,error是给用户看的。不要把给你自己看的东西打到这个里面,比如stacktrace。太长的错误信息会让用户迷惑。error是一个完整的错误报告的索引,人们可以根据error里的内容按图索骥,很快还原现场。&/p&&p&那么panic是什么呢?当你的接口契约上没有error,但是又出错了的时候,怎么办?这个时候panic。panic意味着,给你的用户说,你不用管错误,错了算我的。使用panic,免除了用户对你出问题时进行容错的责任。panic是抛给自己看的。&/p&&h2&你的同事&/h2&&p&你们是一个团队,共同负责一个很大的平台。但是每个人都有自己的分工,负责自己的一块事情。当平台挂了,整个团队需要很快找到谁应该来负责,喊这个人来修bug。你可能就是那个需要来修bug的人。你需要提供足够的信息,让喊你来修bug的同事的工作更轻松。一般一个组织里,专门负责喊人来修bug的角色叫运维。&/p&&p&需要立即喊人来修bug的情况大多数就这两个场景&/p&&ul&&li&A 调用 B,最近一段时间的错误率是否飙升&/li&&li&A 调用 B,最近一段时间的延迟是否飙升&/li&&/ul&&p&当这两个条件被满足的时候。A和B都会被喊来看问题。当A是最终用户的时候,则只有B会被喊来看问题,比如客户端团队。当B是外部团队的时候,则只有A会被喊来看问题,比如网络供应商出问题了。&/p&&p&所以,我们总共需要三个信息,以及若干附加信息&/p&&ol&&li&主叫方:发起调用的功能点名称。大粒度的是模块,小粒度的是函数。&/li&&li&被叫方:被调用的功能点的名称。&/li&&li&是否出错:出错了还是没出错。错误码可选。&/li&&li&延迟:调用耗时。可选。&/li&&li&timestamp:可选。默认为当前时间&/li&&li&其他信息:上下文&/li&&/ol&&div class=&highlight&&&pre&&code class=&language-go&&&span&&/span&&span class=&nx&&countlog&/span&&span class=&p&&.&/span&&span class=&nx&&Info&/span&&span class=&p&&(&/span&&span class=&s&&&metric&&/span&&span class=&p&&,&/span&
&span class=&s&&&caller&&/span&&span class=&p&&,&/span& &span class=&s&&&ValidatePassport&&/span&&span class=&p&&,&/span&
&span class=&s&&&callee&&/span&&span class=&p&&,&/span& &span class=&s&&&Redis&&/span&&span class=&p&&,&/span&
&span class=&s&&&err&&/span&&span class=&p&&,&/span& &span class=&nx&&errors&/span&&span class=&p&&.&/span&&span class=&nx&&New&/span&&span class=&p&&(&/span&&span class=&s&&&some error&&/span&&span class=&p&&),&/span&
&span class=&s&&&latency&&/span&&span class=&p&&,&/span& &span class=&nx&&time&/span&&span class=&p&&.&/span&&span class=&nx&&Second&/span& &span class=&o&&*&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span&
&span class=&s&&&traceId&&/span&&span class=&p&&,&/span& &span class=&nx&&traceId&/span&
&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&一条metric就是一条log。metric本身比error log打印得都更全。比如调用正常的情况下,metirc也会被上报的,而不仅仅只是在出错的情况下。所以 metric 上报是可以替代 error log的。&/p&&p&当然metric不会被直接打给你的同事们看。metric的信息要被多级聚合之后,成为measurement,绘制成曲线,变成告警才是有价值的信息。这些一般有运维的监控平台来负责。&/p&&p&所以,metric是给同事们看的。要在所有可能出错的地方都加上标准化的metric。metric至少要说明谁在调用谁,出错了没有。通过metric构成了一个职责链,按照这个链条,我们可以找到负责的人。比如api出错了,发现是调passport引起的,passport出错了发现是调redis引起的。有这样一个链条,可以快速地让redis团队介入问题处理,避免了逐级踢皮球。&/p&&h2&你自己&/h2&&p&用户通过error找到你的同事,你的同事通过metric找到了你。到你这之后,皮球就没法踢给别人了。你得修复问题。&/p&&p&如果幸运得话,error里的错误消息,加上同事提供的metric,你就能够猜到问题是怎么产生的了。但是不幸的话,你需要&b&更多的信息&/b&。&/p&&div class=&highlight&&&pre&&code class=&language-go&&&span&&/span&&span class=&nx&&countlog&/span&&span class=&p&&.&/span&&span class=&nx&&Debug&/span&&span class=&p&&(&/span&&span class=&s&&&file rotated&&/span&&span class=&p&&,&/span&
&span class=&s&&&traceId&&/span&&span class=&p&&,&/span& &span class=&nx&&traceId&/span&&span class=&p&&,&/span&
&span class=&s&&&file&&/span&&span class=&p&&,&/span& &span class=&nx&&file&/span&
&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&这种普通地日志,就是打给我们自己看的。他的作用就是当问题发生了之后,可以根据一部分的日志维度,找到关联的其他日志做为上下文,重建出故障现场。在这个例子里,traceId就是关联用的维度,file就是故障现场的上下文。&/p&&h2&错误处理方式的选择&/h2&&p&按照前面的分析,我们知道了四种工具的分类:&/p&&ul&&li&error:给使用者的错误消息&/li&&li&metric:一种特殊的日志。统计之后给同事看的。&/li&&li&log/panic:辅助自己定位bug用的&/li&&/ul&&p&然后有以下规则&/p&&ul&&li&所有你自己会出错,或者你的依赖方会出错的场景。你都需要在接口上返回error&/li&&li&一般来说只有纯内存的操作才存在无error的api接口。但凡牵涉了磁盘或者网络,都要返回error。&/li&&li&纯内存操作时遇到了无法解释的情况,比如数组越界。只能panic。因为接口上不返回error,但还是必须要报错。&/li&&li&如果你不能提供更多的错误消息,直接返回底层依赖的error。&/li&&li&如果底层依赖的error,在你的场景里时含糊的。比如json解析失败。你有责任提供更明确的错误消息做为error返回,不能直接返回底层依赖的error。毕竟error是一个索引,如果用户不能按图索骥找到解决问题的人,这个error是无效的。&/li&&li&error的错误消息尽量包含一些编号字段,无论是trace id还是订单号。方便场景还原。&/li&&li&但凡调用了会返回error的函数,都需要打印metric日志。提供caller/callee/error。不仅仅是在出错的时候才打印错误日志,正确的时候也要打metric。什么时候输出到文件和控制台,这个是metric/log框架要考虑的事情。&/li&&li&除了metric之外的日志都是给自己看的。要尽可能的用结构化的方式提供更多的上下文信息。&/li&&li&所有的goroutine都必须在最上层处理panic。防止panic引起程序crash。因为进程重启需要花时间。fail fast 不用这么极端。能够把错误日志告警报出来就行了。&/li&&li&所有的panic都要写入fatal级别的log,并且提供stacktrace&/li&&li&每次临时添加fmt.Println用完又删掉,都是给你的日志基础设施提的一个bug。日志好使的话,为什么需要fmt.Println。&/li&&/ul&&h2&日志级别的选择&/h2&&p&现有的日志框架在日志级别的处理上是不够灵活的。&/p&&div class=&highlight&&&pre&&code class=&language-go&&&span&&/span&&span class=&nx&&countlog&/span&&span class=&p&&.&/span&&span class=&nx&&Debug&/span&&span class=&p&&(&/span&&span class=&s&&&metric&&/span&&span class=&p&&,&/span&
&span class=&s&&&caller&&/span&&span class=&p&&,&/span& &span class=&s&&&ValidatePassport&&/span&&span class=&p&&,&/span&
&span class=&s&&&callee&&/span&&span class=&p&&,&/span& &span class=&s&&&Redis&&/span&&span class=&p&&,&/span&
&span class=&s&&&err&&/span&&span class=&p&&,&/span& &span class=&nx&&err&/span&&span class=&p&&,&/span&
&span class=&s&&&latency&&/span&&span class=&p&&,&/span& &span class=&nx&&time&/span&&span class=&p&&.&/span&&span class=&nx&&Second&/span& &span class=&o&&*&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span&
&span class=&s&&&traceId&&/span&&span class=&p&&,&/span& &span class=&nx&&traceId&/span&
&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&比如我在这里使用了Debug的日志级别并不代表这个log本身一直是不重要的。当err是非nil的时候,它应该是ERROR级别,而不是DEBUG级别了。Debug仅仅是代表在正常的情况下,不要打印到log文件里。&/p&&div class=&highlight&&&pre&&code class=&language-go&&&span&&/span&&span class=&nx&&countlog&/span&&span class=&p&&.&/span&&span class=&nx&&Trace&/span&&span class=&p&&(&/span&&span class=&s&&&file rotated&&/span&&span class=&p&&,&/span&
&span class=&s&&&traceId&&/span&&span class=&p&&,&/span& &span class=&nx&&traceId&/span&&span class=&p&&,&/span&
&span class=&s&&&file&&/span&&span class=&p&&,&/span& &span class=&nx&&file&/span&
&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&设置成了Trace级别之后。一般情况下就扔掉了。但是更好的行为是,如果这个附近没有错误,这个log就是无用的,可以扔掉。如果附近有错误,则应该把log留下来做为故障现场的一部分。现在的日志框架都是逐条处理的。丢弃就直接丢弃了。如果能够在请求级别进行缓存,则出错的时候,可以记录下大量的现场。&br&以上两点是对现有日志框架的吐槽。然后我们应该如何选择日志级别:&/p&&ul&&li&Trace:开发的时候定位问题用的。仅仅可能在开发的时候打开。日志的条数和请求数量n成正比,而且可能是n的数倍。开发的时候只在必要的情况下,才会打开。&/li&&li&Debug:线上定位问题时用的。仅仅可能在线上短暂的打开。日志的条数和请求数量是一个级别。开发时候的默认日志级别。一个日志框架必须在没有任何初始化的时候也能打印出Debug以及之上的日志来。否则在单元测试之类的场景就会很蛋疼。log4j就很蛋疼。对于日志框架来说,没有做任何初始化,就应该自己现在跑在开发者的IDE里。假设日志级别是Debug,输出是stderr,格式应该是人类可读的格式,是最合理的默认值。&/li&&li&Info:正常的线上日志级别。日志的条数要远小于请求数量。只在特殊事件发生时(系统状态发生迁移的时候)才打印出来。&/li&&li&Warn:可预见的错误,但是责任不在我。本质上也是Info。&/li&&li&Error:可以预见的错误发生了的时候使用。所有的metric日志,在error不是nil的时候,日志级别要自动变成Error。&/li&&li&Fatal:不可遇见的错误发生了的时候。也就是panic日志。&/li&&/ul&&h2&标准的错误处理案例&/h2&&p&最naive的例子&/p&&div class=&highlight&&&pre&&code class=&language-go&&&span&&/span&&span class=&kd&&func&/span& &span class=&nx&&doX&/span&&span class=&p&&(&/span&&span class=&nx&&ctx&/span& &span class=&nx&&context&/span&&span class=&p&&.&/span&&span class=&nx&&Context&/span&&span class=&p&&)&/span& &span class=&kt&&error&/span& &span class=&p&&{&/span&
&span class=&nx&&file&/span&&span class=&p&&,&/span& &span class=&nx&&err&/span& &span class=&o&&:=&/span& &span class=&nx&&os&/span&&span class=&p&&.&/span&&span class=&nx&&OpenFile&/span&&span class=&p&&(&/span&&span class=&s&&&/tmp/my-dir/abc&&/span&&span class=&p&&,&/span& &span class=&nx&&os&/span&&span class=&p&&.&/span&&span class=&nx&&O_RDWR&/span&&span class=&p&&,&/span& &span class=&mo&&0666&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&nx&&err&/span& &span class=&o&&!=&/span& &span class=&kc&&nil&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&err&/span&
&span class=&p&&}&/span&
&span class=&k&&defer&/span& &span class=&nx&&file&/span&&span class=&p&&.&/span&&span class=&nx&&Close&/span&&span class=&p&&()&/span&
&span class=&nx&&_&/span&&span class=&p&&,&/span& &span class=&nx&&err&/span& &span class=&p&&=&/span& &span class=&nx&&file&/span&&span class=&p&&.&/span&&span class=&nx&&Write&/span&&span class=&p&&([]&/span&&span class=&nb&&byte&/span&&span class=&p&&(&/span&&span class=&s&&&hello&&/span&&span class=&p&&))&/span&
&span class=&k&&if&/span& &span class=&nx&&err&/span& &span class=&o&&!=&/span& &span class=&kc&&nil&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&err&/span&
&span class=&p&&}&/span&
&span class=&k&&return&/span& &span class=&kc&&nil&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&每次写 if err != nil { return err } 的时候都充满了负罪感。总觉得应该再多做一点什么。我们第一个golang项目上线之后,大家都震惊了。这json.Unmarshal错误到底是哪里报的啊。然后一行一行的猜。然后无比怀念java的异常堆栈。&/p&&p&第二次的选择是,给错误加上一些“特征”。让错误容易定位到代码行:&/p&&div class=&highlight&&&pre&&code class=&language-go&&&span&&/span&&span class=&kd&&func&/span& &span class=&nx&&doA&/span&&span class=&p&&(&/span&&span class=&nx&&ctx&/span& &span class=&nx&&context&/span&&span class=&p&&.&/span&&span class=&nx&&Context&/span&&span class=&p&&)&/span& &span class=&kt&&error&/span& &span class=&p&&{&/span&
&span class=&nx&&file&/span&&span class=&p&&,&/span& &span class=&nx&&err&/span& &span class=&o&&:=&/span& &span class=&nx&&os&/span&&span class=&p&&.&/span&&span class=&nx&&OpenFile&/span&&span class=&p&&(&/span&&span class=&s&&&/tmp/my-dir/abc&&/span&&span class=&p&&,&/span& &span class=&nx&&os&/span&&span class=&p&&.&/span&&span class=&nx&&O_RDWR&/span&&span class=&p&&,&/span& &span class=&mo&&0666&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&nx&&err&/span& &span class=&o&&!=&/span& &span class=&kc&&nil&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&fmt&/span&&span class=&p&&.&/span&&span class=&nx&&Errorf&/span&&span class=&p&&(&/span&&span class=&s&&&failed to open file: %v&&/span&&span class=&p&&,&/span& &span class=&nx&&err&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&k&&defer&/span& &span class=&nx&&file&/span&&span class=&p&&.&/span&&span class=&nx&&Close&/span&&span class=&p&&()&/span&
&span class=&nx&&_&/span&&sp}

我要回帖

更多关于 sitcom 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信