怎么攻击php做的网站,六安市城乡建设网站,wordpress 评论列表,wordpress段子主题点击上方蓝色“Go语言中文网”关注#xff0c;回复「电子书」领全套Go资料有几个学生研究归纳了go编程中的并发bugs#xff0c;发表了一篇(英文)论文#xff1a;《Understanding Real-World Concurrency Bugs in Go》。为你下载好了 PDF#xff0c;关注公众号 Go语言中文网… 点击上方蓝色“Go语言中文网”关注回复「电子书」领全套Go资料有几个学生研究归纳了go编程中的并发bugs发表了一篇(英文)论文《Understanding Real-World Concurrency Bugs in Go》。为你下载好了 PDF关注公众号 Go语言中文网回复 gostudy 获取。在此做一个笔记便于查阅。文章以六个产品级go应用作为研究对象Docker、Kubernetes、etcd、gRPC、CockroachDB、BoltDB总共研究了这些应用中的171个bug研究它们的根本原因并重现这些bugs以及检查它们的修复补丁。最后用两个现有go并发bug检测器测试了这些bug。文章试图回答一个问题对于两种线程/协程间通信机制消息传递机制和共享内存机制哪个更不容易出错文章从两个维度对bug进行了分类bug原因(对共享内存的误用、对消息传递的误用)和bug表现(阻塞性bug、非阻塞性bug)。研究结果及提交日志可以在以下地址查阅https://github.com/system-pclub/go-concurrency-bugsmany concurrency bugs are caused by the mixed usage of message passing and other new semantics and new libraries in Go, which can easily be overlooked but hard to detect.背景 使用共享内存实现同步Go支持协程间共享内存提供了多种传统的同步手段如锁(Mutex)、读写锁(RWMutex)、条件变量(Cond)、原子读写(atomic)。go的RWMutex实现与C中的pthread_rwlock_t不同go中的写锁请求优先级高于读锁。go中还有一些新特性Once保证一个函数只执行一次使用 Once.Do(f) 方法即使这一语句被多个协程调用了多次也只有第一次的时候函数f会被执行。和C中的pthread_join类似go使用WaitGroup来实现等待协程对其他协程的等待。使用消息传递实现同步channel(chan)是go的新特性学习go语言编程的都应该熟悉了。channel分有缓冲和无缓冲两种(buffered and unbuffered)。使用select可以从多路channel中进行选择。当有多路case有效时select会从中随机选择一个去执行这种随机性可能会造成bug。Go引入了几种新机制来简化协程间的交互如用context携带数据传递在不同协程之间还有Pipe可在读协程和写协程之间传递流式数据。这两种都是新的消息传递机制不注意的话可能引起新的并发bug。Go并发模型 在研究并发bug前文章先研究了go中的并发模型。首先统计了那几个应用中创建gorutine的(静态)语句数量(位置数量)如下表img文章觉得喜欢用匿名函数创建gorutine的多些(除了kubernetes和BoltDB)另外还发现C语言版gRPC比go语言版更少创建线程语句。然后文章还统计了各种同步机制的使用比例如下图img从中可以看出共享内存机制的锁还是用得最多啊同时这些机制的使用比例随着项目时间推进是否有什么变化趋势的似乎没有明显变化如下截图imgBug分类 分类如下img从数值看阻塞性bug和非阻塞性bug出现数量差不多。(笔者注对于原因而言从数值上看使用共享内存的造成bug比较多但是这里只统计了绝对值没有和前面共享机制的使用量结合起来考虑比例似乎不大妥当。)对于这些bug文章作者使用相应有bug的版本根据bug报告中的操作尝试重现这些bug结果发现并发bug是很难重现的。从而这些bug存在时间都比较长而一旦被发现一般会比较快地得到解决。bug生存时间统计如下imgBug原因分析 1、阻塞性bug统计如下img具体分析(1)对共享内存保护的失误Mutex28个阻塞性bug由对锁的不当使用造成包括重复锁、以冲突的顺序申请锁、忘记解锁*。这些bug都是传统bug文章觉得传统的死锁检测算法应该能检测出这类bug。RWMutex前面提到过go中的写锁优先级高。这种实现机制可以造成如下bug协程A对同一个RWMutex申请两次读锁但在这两次申请中间协程B申请写锁。此时由于A已经持有了一个读锁而写锁又是排他性的所以B被阻塞。然后A第二次申请读锁时由于B的写锁优先级高所以A的读锁必须排在B的写锁请求之后导致A被阻塞。从而发生了死锁。统计中有5个bug是由这个原因造成。由于在C语言中这种情况不会造成死锁所以参考C语言类似机制在Go中写这样的代码容易导致这样的bug。Wait3个阻塞性bug归因于等待操作无法继续。跟Mutex和RWMutex不同这里并不涉及循环等待。有两个bug是这样的Cond被用来保护共享内存访问其中一个协程调用了Cond.Wait()但是在这之后却没有别的协程调用Cond.Signal()(或Cond.Broadcast())。另一个bugDocker#25384如下图所示使用了一个共享的WaitGroup变量造成bug主要是Wait()放在了错误的地方即第7行修复bug只需要把Wait()挪到图中的第8行(循环外)。img(2)对消息传递的误用Channel对通过channel传递消息的错误使用导致了29个阻塞性bug。很多都跟发送和接收的错配有关。如下图所示在使用第2行代码初始化channel的情况下在子协程执行到第6行代码前如果超时时间到了或者子协程执行到第6行时select的两个case同时可用由于select的随机性而跑到了超时的那个case就会导致finishReq函数返回从而子协程阻塞。这个问题的修复方法是将channel定义为缓冲channel这样无论何种情况子协程都不会阻塞住。img当组合使用go特定类库时channel的创建和协程阻塞有可能被埋在了类库的调用之中。如下图所示行1创建了一个新的context对象 hcancel同时一个新的协程被创建消息可以通过hcancel的channel传递到新协程。如果在行4 timeout大于0另一个context对象在行5被创建并且hcancel指向了新的对象。之后将无法向协程所关联的旧对象发送消息旧对象也没法被关闭。这个问题的避免方法是避免创建额外的context对象。imgChannel和其他的阻塞特性有16个bugs其中一个协程阻塞在Channel操作而别的协程阻塞在锁或等待上。如下图协程1在发送消息到ch时阻塞了而同时协程2却被m.Lock()阻塞。解决方案是对协程1使用具有default分支的select来确保ch不再阻塞。img消息库函数go提供了几种传递消息和数据的库如Pipe。对这些的不正确使用也会造成bug。例如和Channel类似如果一个Pipe未关闭Pipe的两端一个伙伴挂了另一个伙伴等着读或写数据那这是等着读或写数据的伙伴就被阻塞住了。类似的bug有4个。最后关于阻塞性bug文章认为消息传递机制更容易造成更多类型的bug。2、非阻塞性bug统计如下img(1)对共享内存的保护失败已有很多研究发现未保护共享内存或保护错误是造成数据竞争或其他非阻塞性bug的主要原因。本文也发现80%非阻塞性bug都归因于未保护或错误地保护共享内存。但go中的情况和传统编程语言的情况也并非完全相同。传统bug超过一半非阻塞性bug都是由于传统问题造成的就跟在Java、C这些编程语言中一样如原子操作的破坏、顺序混乱、数据竞争。有几个bug是对go新特性的不够理解造成的如Docker#22985 和 CockroachDB#6111 是由于将一个变量的引用通过Channel在不同协程间传递从而造成了共享变量的竞争状态。匿名函数Go语言中在一个函数前加go关键字就可以启动协程这个函数是可以没有名字的(匿名)。在匿名函数之前定义的所有局部变量在匿名函数中都是可见的。不幸的是由于开发者可能不够注意对这些在不同协程中的共享变量做保护从而可能容易导致数据竞争的bug。有11个bug就是这种类型其中9个是父协程和子协程之间的数据竞争2个是两个子协程之间的数据竞争。如下图的一个例子含bug的版本中变量i在父协程和子协程之间共享了开发者想要得到不同的i值所生成的apiVersion但是如果在父协程的for循环结束后子协程才运行起来那所有的apiVersion都将等于”v1.21”。解决方案就是将i作为参数传递到子协程中此时传递的是i的拷贝。imgWaitGroup的误用使用WaitGroup的一个基本准则是Add必须在Wait之前执行。有6个bug是因为违反了这条准则。如下图所示这是etcd中的一个bug这里是无法保证func1中行8的Add一定在func2中行5的Wait之前执行的。解决方案就是将Add操作遇到行6的位置保证要么Add在Wait之前执行要么根本不会执行到idle这个case。img特定库函数go中有些类库的变量是隐式在多协程中共享的。如context就被设计为可以被多个关联协程访问。etcd#7816就是因为在多个协程中竞争使用一个context对象的一个字符串字段导致的。另一个例子是testing包。测试函数只有一个testing.T类型的变量这个变量用于传递测试状态如error何日志。有3个bug就是在测试函数以及测试函数内启动的子协程之间竞争使用testing.T变量导致。(2)消息传递中的错误channel的误用前面也提到过channel的使用需要遵循一定的规则否则就会引起一些bug。如下图所示(Docker#24007)可能有多个协程会运行到这段代码其中可能有多个跑到了select的default分支导致对channel的多次关闭从而引发panic。这种情况可以使用Once.Do将关闭channel的语句包起来保证它只会执行一次。img还有一种类型是将channel和select一起使用当select收到多个case的消息时是没办法保证会执行哪一个的这种非确定性的选择导致了3个bug。下图是一个例子其中f函数执行耗时操作当它执行完之后stopCh的消息和ticker有可能同时到达此时并不一定会执行到11行return语句也有可能执行到case img特定库函数一些库函数内部会使用channel也可能导致非阻塞性bug。下图是一个与time包有关的bug。开发者想实现的是要么收到Done信号要么超时然后再返回。但是含bug的版本先创建了超时时间为0的timer然后再判断参数dur是否大于0 大于0的话修改timer。但是当dur为0的情况下timer实际上一开始就被设置为有信号了可能导致函数过早返回。解决方案是不要让timer过早创建。img非阻塞性bug的检测 Go提供了数据竞争检测在build的时候使用 -race 标志即可启用。文章的一些结论是消息传递机制也容易造成bug情况并不比共享内存机制好。消息传递机制更多地会造成一些阻塞性bug比较少造成非阻塞性bug而且可以用于解决由于共享内存导致的非阻塞性bug。关于bug检测目前很多在传统语言中针对共享内存的检测算法在go中也是适用的但是针对go的消息传递机制所引起bug的检测还需研究。译者Darlzan译文链接https://blog.csdn.net/notjusttech/article/details/88294964推荐阅读Socket Server的N种并发模型汇总福利我为大家整理了一份从入门到进阶的Go学习资料礼包(下图只是部分)同时还包含学习建议入门看什么进阶看什么。关注公众号 「polarisxu」回复 ebook获取还可以回复「进群」和数万 Gopher 交流学习。