当前位置: 首页 > news >正文

快速建站属于saas吗建站工具大全

快速建站属于saas吗,建站工具大全,百度云wordpress建站,附近的广告设计和制作译者个人领悟,一家之言: 并发和并行确实可以明确区分出来,因为cpu的速度非常快,在执行一个任务时经常要等其他组件,比如网络,磁盘等,如果一直串行等待这样就会造成很大的浪费. (就类似于烧水的同时,可以切菜,不用等烧水完成了才去切菜,我可以烧一会水,火生起来了水壶放上了,随… 译者个人领悟,一家之言: 并发和并行确实可以明确区分出来,因为cpu的速度非常快,在执行一个任务时经常要等其他组件,比如网络,磁盘等,如果一直串行等待这样就会造成很大的浪费. (就类似于烧水的同时,可以切菜,不用等烧水完成了才去切菜,我可以烧一会水,火生起来了水壶放上了,随后这段时间就能去切菜,切着切着菜发现水烧开了,就又可以切换到烧水,并发就是高频高速做这样的切换) . 即并发就是做这样事情的, 不用一直等着,可以去做别的事,因为cpu一直切换,单线程,或者说单核cpu也可以不停切换. 以Go来说, go func1, go func2 ... go func100 和 for循环里启动100个协程 for i:0;i100;i { go func666 }, 其实都叫并发, 区别是 go func1,go func2 需要是互相不依赖的才会这样写,而for循环里启多个func666, func666相互之间更不存在依赖关系了(其实和外面go func666, go func666 …写100个等效) 即当go func1,go func2…go func100 无相互关系时, 和循环里启100个go func666 两者本质一样(其实效果也一样,只不过for里写方便点,代码量少一点). (面试要求中常说的高并发,其实等同于高QPS,一定程度上模糊了其概念和作用) 如果是单核情况下的并发,本质上还是同一时间,只能做一个事,不可能既烧水又做饭 (cpu高频,看起来是能同时做), 即现在这100个协程,由GMP机制不停调度, 但在任何一个时刻,能得到执行的,还是最多只有1个协程 (但这种情况还是可以导致map等有并发问题…). 即func1,func2…func100同一时间点只能有一个在运行,或者for循环里启100个协程,同时也只有一个在执行(go淡化了协程id的概念,不给程序员暴露,但其实是有协程id的~) 而并行,必须得是多核cpu. 即无多核,不并行…因为是有多核,所以同一时刻,可以做多件事情.多核cpu情况下的并发, 就算是并行了(并行不一定都在执行并发相关的代码,大概率可能在做别的; 比如多核cpu,一个核在处理并发相关的逻辑块,另外的核在处理其他程序比如qq,或者哪怕在处理同样说的上面这个100个协程的go程序,也可能在处理这个程序并发无关的模块,所以有后面这句话的最多), 还是以上面100个协程的case来说, 比如有4核心,那同一时刻,确实可以有最多4个协程得到执行… 即1号协程的func1,2号协程的func2 … 是可以同一时间点都运行的 而异步, 异步帮助实现并发,在多线程环境中是实现并行的一种方法。 所以说,和串行相对的概念不是并行,而是并发… 串行是从上到下func1完了做func2,再func3…而并发是这些没有前后关联的func可以同时做, 在某个func陷入停滞时,去做其他func 而不会阻塞在那干等着(如何才能判断某个func陷入停滞? go里面是每个协程都高速切换,最多有10ms执行机会) 本篇内容是根据2019年12月份Concurrency, parallelism, and async design音频录制内容的整理与翻译, Go 在设计时就考虑到了并发性。这就是为什么我们有 goroutine、通道、等待组和互斥锁等语言原语。如果使用得当它们非常强大但如果使用不当它们可能会非常复杂。 Roberto Clapis 再次加入团队为大家带来异步智慧。不过别担心我们会以串行方式进行。 过程中为符合中文惯用表达有适当删改, 版权归原作者所有. Mat Ryer 你好欢迎收听 Go Time。我是 Mat Ryer。今天我们要讨论的是并发编程。Go 是为并发设计的这就是为什么它提供了 goroutine、channel、WaitGroup、Mutex 等语言原语。它们在正确使用时非常强大但如果使用不当也可能非常复杂。为了帮助我今天理清这些思路我邀请了 Johnny Boursiquot。你好Johnny Johnny Boursiquot 你好Mat。 Mat Ryer 你怎么样 Johnny Boursiquot 我来帮忙抽丝剥茧。 Mat Ryer 很好太好了。不过你不需要一个人拉这些线……我们还邀请到了 Jaana B. Dogan。你好Jaana。 Jaana Dogan (译者注,压测工具hey的作者): 你好大家好。 Mat Ryer 你今天怎么样 Jaana Dogan 我刚喝了今天的第一杯咖啡…… Mat Ryer 恭喜啊。 Jaana Dogan ……所以我还稍微有点慢热。 Mat Ryer 是的慢慢来。还有一位嘉宾如果你听到了他的声音……他是 Roberto Clapis。你好Rob你怎么样 Roberto Clapis 你好我很好。今天话题会比较复杂所以我打算轻轻地探讨…… Mat Ryer 哦听到你这么说我很高兴。 Johnny Boursiquot 不错不错……我想我们今天开了个好头。 Roberto Clapis 谢谢 Mat Ryer [笑声] 好那我们就开始吧C 语言是在 70 年代设计的当时的计算机只有一个核心远不像现在的计算机那么复杂……而现代的计算机有多核处理器因此同时处理多个任务的潜力更大了无论这意味着什么……所以在设计 Go 的时候他们当然考虑到了这些情况因此我们有了 goroutine、channel 和其他这些原语。Go 因为并发编程而闻名实际上当你谈论编程语言时Go 和并发几乎是同义的。你觉得 Go 配得上它在并发编程方面的声誉吗 Roberto Clapis 我觉得配得上。实际上在使用 Go 之前我已经用了很多年的 Python。有一次我需要在大学的集群上使用所有 36 个核心来解决一个问题……用一门没有为并发设计的语言来完成这个任务真的让我头疼不已。于是我想“好吧为什么不学一门可能让这变得更简单的语言呢” 用 Go我只写了大约 100 行代码就解决了问题。 Mat Ryer 哇。 Jaana Dogan 我来自 JVM 背景每次我们开始这种大规模、流程密集的任务时我总是抱怨它占用了我所有的处理能力……但听起来这反而是件好事而不是手动完成这些工作…… Mat Ryer 给不熟悉 Go 的听众快速概述一下我们今天可能会使用的一些原语和术语确保大家知道我们在讨论什么。你可以把 goroutine 想象成另一个正在运行的线程。实际上它不是线程但你可以把它想象成你的主应用程序在运行时就是一个线程而如果你想在后台做一些工作你可以启动一个 goroutine 来完成这些任务。实际上你可以启动很多 goroutine 去执行它们的工作理论上它们会尽量利用可用的硬件资源完成任务。 然后当然我们还有 channel它允许 goroutine 之间进行通信……它们非常酷……你可以以安全的方式发送和接收信息。 因为如果这些 goroutine 同时访问同一片内存你可能会遇到问题。 所以我想这些是最基本的两个东西。还有一个 go 关键字它用来启动 goroutine。你可以调用一个函数并通过它启动一个 goroutine。任何想了解更多这方面知识的人都可以在你喜欢的搜索引擎上搜索一下比如 DuckDuckGo或者其他的搜索引擎。好了我的概述到这里…… Johnny Boursiquot 我觉得还应该谈谈适合并发的场景。并不是所有的问题都需要你使用并发编程。通常当你编写一个程序时你会问自己“这个程序的哪些部分可以并发运行或者哪些部分可以独立于其他部分运行” 你通常是试图确定哪些部分可以独立工作而不依赖于其他部分。如果你没有任何部分依赖于其他部分的结果那么这些情况通常是并发可能对你有用的地方。 goroutine 本身——你实际上不需要使用 channel 的消息传递机制来利用 Go 的并发仅仅使用 go 关键字就可以让你开始。真正的关键是当初学者试图理解“Go 中的并发是如何工作的”时他们通常会发现“哦原来主程序本身也是一个 goroutine”而如果我要启动其他的 goroutine它们需要以某种方式互相等待。你的主 goroutine 需要等待其他 goroutine 完成工作然后你的程序才能结束。 这就是为什么“如何将这些东西串联起来如何正确使用 Go 的并发”变得非常重要的原因。我认为我们在社区中应该花更多时间去教育初学者教他们如何在 Go 中思考和理解并发。 Roberto Clapis 我非常喜欢 Go 的一个地方是并发与并行是分开的你不用去刻意思考它们。 比如当你使用 HTTP 包时每次你的 handler 被调用它都会在一个独立的 goroutine 中运行。但当你编写代码时它感觉是同步的你总是需要一个独立的执行线程但你不需要改变你的思维模式来正确使用它。你不需要使用异步操作或者其他不同的原语你只需编写你想要的代码它甚至不需要与其他东西并行运行……因为比如你只在一个核心上运行你可能只想让它这样运行然后你就写代码。 我们可能应该多放出一些材料教大家如何思考和学习并发但我真正喜欢的是你可以非常容易地开始只需编写同步代码你不用担心其他东西。 Jaana Dogan 是的我觉得将同步代码迁移到并发代码也很容易。Go 的魔力在于 select 语句……有人抱怨 select 的行为不太容易理解但除此之外它真的非常易读而且没有什么魔法。我觉得这是我写并发代码时最舒服的语言之一其他人也能很容易理解我在做什么……所以 Go 目前肯定是我进行并发编程的最佳工具。 Mat Ryer 是的我同意你对 select 语句的看法……当你真正掌握它时它非常强大。我最近常用的一个方法是检查 context 是否已经完成。如果我正在一个循环中做一些工作我会在某处设置一个 select 块检查 context 是否被取消这样我就可以在任务被取消时中断工作并优雅地退出……当你把它和 HTTP 请求的 context 结合起来时这非常不错因为这样一来——如果用户在浏览器中取消了请求理论上你为准备该请求而进行的工作也会停止……这种节省可能微乎其微但知道程序在做这样的事真的让人感到非常满意。 译者注: 可参考 Go context.WithCancel()的使用 Jaana Dogan 你觉得 Go 能不能在 context 方面做更多的内置支持我觉得有很多样板代码……你需要确保 context 没有被取消没有超时等等……你觉得有没有什么改进空间也许 select 语句可以自动处理某些情况或者其他什么……我现在只是随便想想…… Mat Ryer 是的我不知道这确实是个有趣的问题。我想过让 context 成为语言的一部分——当然这有点违背 Go 的风格因为 Go 喜欢显式编程…… 但我能想象 context 始终存在而且你始终可以取消它子函数和其他功能也会以同样的方式被取消。你几乎可以想象它。这有点像其他语言中的异常处理机制反过来工作。所以我不确定这种想法会得到多少支持……但有时我确实发现自己在每个方法中都传递了 context几乎每个地方都传递因为甚至我的日志记录可能也需要 context所以在这种情况下所有东西都需要它我不太喜欢这样。但话说回来这样做是显式的而且你不能在没有 context 的情况下调用方法。 Roberto Clapis 我希望自动化的 context 不会成为现实……我喜欢在启动 goroutine 时知道它什么时候开始什么时候结束。举个例子我获取了一个 mutex并用 defer 释放它。这没问题。然后我改变了一些状态然后做了其他事情……如果我遇到错误我希望能够回滚我已经做的事情。你并不总能使用 defer有时候当你编写事务性代码时你希望确保你的操作不会被中断……除非程序崩溃那样什么都不会被提交你确实想知道程序是如何退出的。 我真的想知道我写的代码何时会被中断我想控制这种情况何时发生以及如何发生。如果 context 被取消我希望有机会说“好吧那就回滚我已经开始的操作。” Mat Ryer 是的这是个好观点。select 块的一个好处是它确实是一个块。它有 case然后在某些条件下执行代码……所以你确实有机会按照你描述的那样进行一些清理工作……而且它是显式的。 Roberto Clapis 是的。 Mat Ryer 你提到你想知道 goroutine 何时结束。有没有人有什么技巧来找出 goroutine 已经完成了 译者注: 可参考 Go语言通知协程退出(取消)的几种方式 Roberto Clapis 我觉得无论如何你都应该知道……我见过很多程序和项目变得无法维护就是因为它们只是在启动 goroutine 并让它们运行而没有处理它们的优雅关闭。有人也有类似的经历吗 译者注: 可参考 Goroutine泄露的危害、成因、检测与防治 Mat Ryer 嗯当你的程序结束时它们都会关闭对吧[笑声] Johnny Boursiquot 希望如此…… Jaana Dogan 所以你的程序崩溃了…… Johnny Boursiquot ……否则你就有问题了。 Mat Ryer 是的。 Jaana Dogan 你知道有一种策略是定期让程序崩溃以便释放那些资源……所以这也是个合理的观点。 Johnny Boursiquot [笑] 只要重启一下就好了…… Roberto Clapis 是啊。我都在想我们为什么还需要垃圾回收器了。[笑声] 你不需要…… Mat Ryer 是的重启就好了。 Jaana Dogan: “这就是PHP的本质。” [笑声] Johnny Boursiquot: 你问我们是否知道或者能否知道goroutine何时结束——我认为没有任何生命周期事件或者类似的东西可以让你知道goroutine何时结束。然而通常你需要做的是这也是涉及到通信的地方开始显现的。例如如果你在主goroutine中想知道你启动的另一个goroutine何时完成工作并且你想等待它完成Go语言中有一些机制可以实现这一点。比如使用WaitGroup就是一个很好的方式它可以阻塞直到其他goroutine完成工作然后你就可以继续执行。 这也是channel派上用场的地方。如果你启动了一个goroutine去执行某些工作当你初始化它时如果你能传递一个channel用于接收工作完成的通知不论是你的主goroutine还是其他需要知道该事件的goroutine都能收到通知。因此这种通信管道——无意双关——是你了解工作状态的必要手段。你可以通过这种方式知道goroutine的工作状态比如它是否完成了。除此之外还有其他方法可以实现这种通信但没有直接的方式告诉你“这个goroutine是否结束了”。你需要围绕它进行一些监控。 Jaana Dogan: 你提到生命周期确实没有相关的API可以使用……这是设计上的选择目的是让用户体验更加简洁和紧凑。不过过去几年里我遇到了很多场景我确实想施加一些限制或者想将某些goroutine或底层操作系统的线程绑定到某些处理器上等等。你是否遇到过类似的问题你觉得Go在执行方面非常简单它会帮你处理所有事情并且将问题封装起来但也在某种程度上限制了用户……你对此有何看法 Roberto Clapis: 我认为它确实在某种程度上限制了用户但这是好事。从某些方面来说Go提供的抽象层让你能够构建更好的抽象。例如errgroup是我到处都会用到的东西因为当你取消上下文时可以立刻知道所有工作何时结束并传播关闭信号。但与此同时如果你想深入到底层就像你说的那样……比如你想要降低权限。如果你希望某个goroutine说“好这个程序现在以root身份运行但它不应该再是root身份了。” 降级权限的操作仍然存在问题而且这个问题已经存在了九年……因为它有竞争条件并且存在一些问题。Go并没有给你底层线程的精细控制例如无法将线程固定在某个核心上比如在图形处理的场景中。现在唯一能做到的方式是从主函数中说“好把我固定在这个OS线程上永远这样我将负责绘图。” 这非常笨拙。 译者注: 可参考 ErrGroup-有错误返回的Goroutine控制 Jaana Dogan: 是的没错。而且随着NUMA和所有这些新的调度控制方式的出现你可以将自己固定在某些处理器或某些处理器组上。人们确实会为了这些精细的优化这样做因为你可能对任务本身或你正在运行的东西了解更多通过将它们分组或其他方式……这确实合理。我曾经在实验性地使用Go做这类事情但这真的是一个非常棘手的话题。你唯一能做的就是将自己锁定在OS线程上然后通过一些C库对OS线程进行一些控制……这很有趣。 译者注: “NUMA node(s)”非一致性存储访问节点是一种计算机体系结构设计用于处理多个物理处理器和内存子系统之间的数据访问。NUMA是一种在多处理器系统中优化内存访问性能的方法。 Roberto Clapis: 我想是Ristretto的作者们提到他们需要一个线程本地存储但Go没有提供这个功能所以他们使用了sync.Pool虽然是有损的但他们仍然觉得可以接受因为即使有损也比和其他线程共享东西要好……我猜当你走到这一步时可能你在用错东西。 [笑声] Johnny Boursiquot: 公平地说他们确实提到在底层确实有某种形式的线程本地存储但作为语言用户你无法访问它。 Roberto Clapis: 是的确实如此所以他们才这么做。 Jaana Dogan: 实际上有一些人正在滥用底层存储我看到有些公司在基于这个做一些自动化的监控……类似于某种执行跟踪类型的东西。这非常有争议当然也不推荐但人们正在逆向工程并劫持这个功能……这挺吓人的。 [笑] Mat Ryer: 那么对于Go中用于并发工作的某些包你们有什么看法我想到的是sync包。sync.Once是一个非常有用的小工具。基本上你给它一个函数它保证该函数只会被调用一次。所以在某些场景下非常有用比如一个web处理器如果你需要在第一次调用时做一些初始化工作但你可能想延迟到第一次调用时再执行——那么你可以把它放在处理器内部……但是由于每个请求在Go中都会启动自己的goroutine两个请求可能会同时到达启动两个goroutine它们都试图执行初始化工作。或者它们在检查是否为nil或者它们在做某些其他操作结果可能会互相冲突。你可以使用sync.Once来防止这种情况发生。 它的工作方式是第一个到达的会执行函数其他的则会等待该函数完成然后才会继续执行。非常有用很实用是个很棒的小工具……不过还有一些更底层的工具对吧 Roberto Clapis: 是的比如sync.Map我认为这是Go标准库中被滥用最多的数据结构。 Mat Ryer: 真的吗 Roberto Clapis: 是的因为人们以为它只是一个线程安全的哈希表……其实并不是。我认为线程安全的哈希表就是一个带有互斥锁的map仅此而已。sync.Map实际上是为了减少缓存争用。我见过很多人到处使用sync.Map但实际上他们需要的只是一个带有保护机制的普通map。sync.Map只有在你注意到你的缓存争用太多并且读操作远多于写操作时才有用……我认为这就是它的用途。它的文档里甚至写着“不要使用这个。” Sync Atomic也是如此明确写着“不要自作聪明”。 (译者注: 即适用于 读多写少 的情况) Jaana Dogan: 是的我觉得是命名问题……它叫sync.Map所以我觉得它的名字并不够自描述。这是主要原因。因为Go文档里明确说明“实际上大多数时候你需要的是一个普通的Go map。” 但是它的名字并没有表明这一点。它应该被叫作sync.某某map。 Johnny Boursiquot: 所以对于听众来说建议是基本上只需要使用互斥锁来保护map的访问对吗Rob你是这个意思吗 Roberto Clapis: 是的基本上就是这样直到你意识到互斥锁是个问题之前不要切换到其他东西。 Mat Ryer: 那这就挺有意思了。Johnny你可以告诉我们什么是map和互斥锁吗它们是如何工作的 Johnny Boursiquot: 好的。默认情况下你创建的普通Go map并不是线程安全的。所以可能会有多个goroutine试图同时往同一个键写入数据……这种情况。对于读操作可能还好但通常你会希望限制goroutine的数量确保在任何时候只有一个goroutine在写入或读取map的内容。这就是互斥锁——“互斥”的缩写——的作用。它保证在任何时候只有一个goroutine能够访问或修改map的内容。 所以我们讨论的就是“sync包中的Map类型是否提供了这种功能” 回应Jaana的观点——它的名字看起来像是应该提供这种功能……但其实并没有。你应该使用一个普通的map但需要引入一个互斥锁来处理可能的争用问题。 Mat Ryer: 是的谢谢。正如Johnny所说如果你想访问这个map你需要锁定互斥锁访问完之后再解锁……如果其他东西在你锁定的时候尝试锁定这个互斥锁它们会等待你解锁。互斥锁是同步点它们确实会造成争用。我们说我们有一个并发程序但在这些点上它不是并发的。你必须在这些点上等待某些事情发生……这让思考变得有些棘手。 Roberto Clapis: 我想打断一下……你应该先锁定互斥锁接着使用defer解锁然后再访问map。 Johnny Boursiquot: 哦我们来谈谈defer吧。Mat你一直想谈谈defer对吧[笑声] Mat Ryer: 我爱defer。我真的想做一期关于defer的完整节目但我想我们只能在这个季末做了对吧……不能在季中做…… [笑声] 它必须是最后一集。 Johnny Boursiquot: 关于defer有趣的是最近在性能上做了很多改进。我记得以前使用defer时有一些基准测试显示defer会带来性能损耗尤其是在大量使用defer的情况下。现在这种情况有所缓解。我不知道它是否完全不是问题了但defer现在快多了。所以Rob当你说“你应该锁定并且使用defer解锁”时我想“是的我现在完全支持这个。” Roberto Clapis: 是的。现在几乎不可能测量出defer所需的时间。如果你在函数中只有一个defer语句并进行测量你会得到一些波动的结果。有时甚至会发现defer更快因为现在几乎无法测量它的开销了。 Mat Ryer: 那是不是在这种情况下defer被编译器优化掉了因为如果它只是一个普通函数体中的defer而不是在循环中……如果它在一个for循环中那么你当然得等到运行时才能知道会有哪些需要defer的操作。但通过静态分析你可能可以查看一个函数并说“好我可以看到所有的退出点所以我会在那些地方调用所有的清理方法。” 我不知道它是否是这么做的还是它优化了整个机制…… Roberto Clapis: 这并不容易因为有panic的存在。你可能看到了返回点但你还需要检查所有可能导致panic的语句因为你需要在panic发生时执行defer语句……此外循环体可能会展开所以你实际上可能会提前执行你本来要defer的几个语句……所以编译器里有很多黑科技而我认为这是最有趣的部分之一。 Jaana Dogan: 我刚刚读到如果你不使用recover性能会有额外的损耗也许是因为他们在做一些优化……但如果你需要recover那么问题可能会变得更加复杂。我不太清楚具体是怎么运作的但你知道的…… [笑] Mat Ryer: 但从可读性角度看defer绝对是无可争议的赢家。当你打开一个文件并检查错误然后说“好defer关闭文件”所有与打开和关闭文件相关的操作都在同一个地方……而且当你忘记关闭某些资源时这也很容易发现因为你就在那个区域查找。它就在你打开文件的附近。所以我觉得从可读性上来说它绝对是赢家对吧 Johnny Boursiquot: 通常我听到有经验的开发者像你们这样说“使用defer因为可读性好而且你不想忘记关闭文件句柄”或者类似的资源管理问题。但我会说有时——我两种方式都用过是的我倾向于使用defer……但同时取决于我正在处理的函数有多大以及我在里面做了多少事情如果我打开一个文件我可能会选择在做完2-3个额外操作后明确地关闭文件而不是使用defer。 所以总的来说确实应该使用defer但我不认为它应该被当作金科玉律。 Jaana Dogan: 的确如此这取决于具体的任务。在某些监控库中我们明确不想使用defer……但如果我有超过10微秒的工作要做或者其他什么的……我不在乎我会直接使用defer。 Mat Ryer: 是的。当然另一个好处是如果你使用defer那么无论你从函数的哪个地方退出资源清理都会被执行。这是唯一值得说的事情。所以如果你要打开几个文件并且要做一些更加复杂的事情那么它确实会有所帮助……但是的我想像所有事情一样这取决于具体情况不幸的是…… Johnny Boursiquot: “这取决于……” 这是我们对一切问题的守护神。 Roberto Clapis: 是的。 Mat Ryer: 的确如此。 Roberto Clapis: 那么关于并发呢 [笑声] Mat Ryer: 哦对了……我告诉过你我们可以做一期关于defer的完整节目。 Roberto Clapis: 我相信你可以。实际上当你提到defer时我说我们应该和select一起讨论……因为我认为它非常优雅大多数人说Go的channel很棒但归根结底channel不过是一个带有互斥锁的队列。而select完全是另一回事它非常难以实现。我认为select才是真正的美丽所在。 Mat Ryer: 这很有趣因为从外部看它似乎非常简单你只需要想“好有一些事件会发生我会把每个事件作为一个case。” 它看起来确实相当简单。但它也非常强大。 Roberto Clapis: 这就是Go的美丽所在。 Mat Ryer: 是的没错。 Jaana Dogan: 我认为Go语言中最主要的并发特性是select语句而不是其他东西。这就是神奇的地方它看起来对你来说非常简单……但实际上实现起来非常复杂。 Johnny Boursiquot: 我想深入探讨一下这个问题。我见过一些代码其中有select语句并且有多个不同的case。有时会有默认情况default有时没有……有人能解释为什么有时会有默认情况而有时没有吗在select语句中有默认情况与没有默认情况有什么影响 Roberto Clapis: select用于从通道channel中接收和发送数据select会阻塞直到其中一个case可以执行。如果你有一个默认case它就像switch语句一样。基本上如果没有其他case可以执行select将继续执行程序。这个特性需要一点时间适应因为我见过有人在循环中使用默认case……他们只是不断地尝试获取一些工作然后“嗯工作还没准备好我们再来一轮”实际上它本该阻塞。而在其他情况下人们在检查上下文取消时没有默认case这会阻塞所有内容而这很难调试因为——比如HTTP处理器不会检测到死锁类似的情况。 所以是的有默认case就不会阻塞没有默认case就会阻塞。阻止程序继续执行的最佳方法是使用一个空的select。 译者注: 使用selct {} 也许是比time.Sleep(1e9*86400) 和 for{} 更好的阻止程序退出的方式 Mat Ryer: 是的……有时这很有用。有时候你确实希望程序一直运行而不会消耗所有资源……即使在一个for循环中也比直接阻塞在select中做更多的工作对吧 Roberto Clapis: 是的。实际上我觉得很有趣的是如果你查看运行时的源码运行时会构建一个依赖图。所以当某个goroutine准备好执行工作时运行时会说“好吧这是下一个要调度执行的”。如果我有一个空的select这是一种向运行时传达“这个永远不会准备好”的方式因此运行时就不再处理它了。它会停在某个地方并且保持在那里。 Mat Ryer: 太棒了。 Johnny Boursiquot: 所以空的select就像一种人道的方式在告诉程序“我希望它停下来但这并不是说所有goroutine都进入了睡眠状态……我是真的希望一切都停下来。” Jaana Dogan: 你知道的你只是希望当前的goroutine挂起。其他一切都会继续运行。 Mat Ryer: 是的。如果你有一个主程序并且启动了五个线程这些线程会在程序中持续工作我可以看到你可能会在主线程中使用空的select……不过如果你考虑上下文你可以通过CtrlC捕获信号并取消上下文。这样的话你实际上可以优雅地从CmdC中关闭程序并且可以编写一个模式使第二次按下信号时程序会真正终止。这是一个非常不错的小技巧……类似的东西。 Roberto Clapis: 是的。只需要一个一位的通道。这非常好用。 Mat Ryer: 是的一个带有一个缓冲空间的通道。 Roberto Clapis: 是的。 Mat Ryer: 当你使用这些信号通道时……这些通道并不打算传递任何信息你只想发送一个事件信号比如“我完成了”之类的信号你会使用什么类型你有最喜欢的吗因为我有一个最喜欢的……这是个已经预设好的问题我只是想告诉你我最喜欢的是什么所以我们快点说完你的……[笑声] Roberto Clapis: 一瞬间我以为你要讨论缓冲通道我想“哦这是个有争议的问题……”但你找到了一个更好的问题。 Johnny Boursiquot: [笑] 好吧有一个流行的习惯用法是使用空结构体作为消息传递的机制。因为它几乎不占用任何内存没有分配基本上……你只是发送信号只是一些简单的信号。初学者可能会倾向于使用布尔值我见过有人使用整型我还见过人们通过通道传递错误信息作为信号机制。 我并不是说这些机制是错误的。有时你从信号中接收到的值——基本上你将其既作为信号也作为你要处理的值这取决于你的具体情况……但通常如果你想要零分配类型的机制仅仅是发送一个信号那么空结构体是你的好朋友。 Jaana Dogan: 这也值得一提具体情况确实有所不同。如果这是一个公开的API比如信号包或者有不同的事件等我认为定义类型是有意义的你可以为信号定义一个类型并且可以从该包中导出更多预定义的信号等。但如果它是一个自包含的东西使用空结构体是完全可以的。 Mat Ryer: 是的空结构体的好处是你无法放入任何信息。它非常清楚地表明了它的用途。我也见过布尔值的使用但我总是不知道我传递true或false是否重要感觉好像有些API需要我传递……但用空结构体就非常清楚它只能作为信号使用因此我喜欢用它来向程序员传达信息。这有助于提高代码的可读性。 Roberto Clapis: 发送false的话我是—— Johnny Boursiquot: [笑] 是零值吗 Roberto Clapis: 是的使用布尔值很危险。[笑声] 因为如果你收到true那是一个信号。但如果你收到false…… Johnny Boursiquot: 你就不知道了。[笑声] Mat Ryer: 这是我在Go Time节目中听到的最极客的一段话至少是在我主持期间…… Johnny Boursiquot: 我们都笑了因为我们都知道你在说什么。[笑声] Mat Ryer: 我们需要更多这样的片段。 Roberto Clapis: 目前我做的大部分工作是代码审查。现在我读的代码比写的多得多。当我看到有人使用map存储某些布尔值时我总是问“如果你得到false但键不存在呢” 对于通道来说也是一样——“你想告诉你的用户什么” 或者像大小为50的缓冲通道。我能理解一两个缓冲但当它开始变成100时我就需要一条注释告诉我为什么了。 Mat Ryer: 是的。 Johnny Boursiquot: 这就像是把它当作队列…… Mat Ryer: 可能是在做某些性能调优或其他操作但你说得对它隐藏起来了看起来很奇怪……没人敢碰它因为你会觉得“为什么是50呢” 这有点像《迷失》里的那些数字当他们不知道是否应该把数字放在那里时……你就是无法停止这么做以防万一…… 译者注: 《迷失》是一部美国超自然电视连续剧,4 8 15 16 23 42这组神奇的数字多次出现在剧情中。 戴斯蒙曾守候小岛多年就是为了每隔108分钟按一次这组数字好让人类不会灭绝 Johnny Boursiquot: 那么让我们谈谈并发尤其是在库中使用并发的情况。有一种惯用的做法是你可以在库中使用并发但完全隐藏它让库的使用者看不到这种并发……同时也有一种说法建议让库的使用者来决定如何使用你的库来组织并发操作。你可以同步执行也可以跨越边界进行并发操作。 所以如果你要在库中使用并发最好在内部整齐地处理它但在其他情况下你应该让库的使用者自己组织并发操作。如果你的库可能会并发操作它应该接受一个通道用于返回信号或并发操作的结果。我见过各种各样的建议……我很好奇你对此有何看法。 Roberto Clapis: 我不喜欢默认异步的API。 Johnny Boursiquot: 嗯。 Roberto Clapis: 每次我看到一个库比如接受一个通道并通过该通道发送结果……甚至更糟的是你调用一个函数它返回一个通道——我是这个做法的反对者。因为我总是要阅读代码来确认如果我取消了上下文他们是否会每次发送时检查取消或者我需要排空通道如果通道填满他们会阻塞吗这些问题经常出现……我更喜欢我的库是同步的。我不喜欢Promise机制。 Jaana Dogan: 我完全同意这一点。我认为一切都应该尽可能阻塞……因为在Go中组织这些并发操作非常容易我完全同意将这种精确的控制权交给用户更加有价值。一些库实际上在同一个包中提供了两种不同的API它们实际上是在重复相同的API这是完全没有必要的因为在Go中组织并发和组合操作是非常容易的。 Go语言目前做得不太好的一点是没有一个容易的方式——无论是在Go文档中还是其他地方——来清楚地标注“哦这个操作实际上会在另一个goroutine中运行”等等。比如HTTP包——每个处理器都会在不同的goroutine中运行你可能需要在Go文档中留下评论……但这并不是很直观或者有些人就会忽略掉……我认为我们需要一种更好的方法来解释底层实现是如何从并发的角度工作的。抱歉我稍微改变了一下话题…… Johnny Boursiquot: 不不没关系…… Roberto Clapis: 我同意。一种情况是你调用一个函数它可能会启动goroutine但只要它在返回之前收集它们那就没问题。但如果那些东西继续运行或者它会在另一个goroutine中运行你传递的闭包你可能希望知道——比如filepath.Walk函数你可能想知道它是否会并发运行因为你可能在闭包中引用了一个变量而你不希望这个变量在多个线程之间被修改。嗯其实它是同步的但如果我们有一种方式来说明“这不会导致同步问题”那就好了。 Jaana Dogan: 是的。在Go语言的初期我见过很多人实际上为他们试图从多个闭包中访问的某些内容使用了互斥锁……但实际上根本不需要这样做因为库已经给出了保证同一时间内只有一个函数会被执行等等……但除了在Go文档中添加这些信息之外几乎没有其他方法可以解释这一点而这并不直观。 Mat Ryer: 是的这很有趣。这让我想到了一件事那就是每当你编写并发代码时最好把它保持在非常局部的范围内让所有并发代码都集中在一起……然后调用函数去执行其他工作。不要试图在同一个地方处理所有工作这样页面会拉长你的并发代码会分散在各处…… 此外像通过指针传递互斥锁之类的事情——如果你能避免这种情况只在一个地方使用互斥锁并在一个函数中处理所有并发操作那就更容易维护也更容易以后理解。经验告诉我这样做更好所以我现在倾向于这样做——我倾向于把所有并发代码集中在一个地方……如果使用WaitGroup来做一些工作我会调用一个函数去执行实际的工作。这样我的并发代码就不会被这些操作搞得混乱不堪。 Roberto Clapis: 我完全同意。 Johnny Boursiquot: 所以我们在使用“并发”这个词时是非常谨慎的对吧所以当你第一次学习Go时你会了解到并发并不等于并行。通过编写并发代码你允许系统并行运行你的代码但这并不是你可以控制的。 我记得Rob Pike曾做过一个演讲标题是《并发不是并行》这个演讲揭示了这个机制。那么你有没有遇到过并行不是正确选择的情况比如你希望没有并发代码最终在并行执行有没有遇到过你没有预料到的竞争条件或者其他问题 Roberto Clapis: 我曾经在处理init函数时遇到了一个大问题因为我在代码中启动了goroutine并在这些goroutine返回之前阻塞了程序。但在init函数中你不能启动goroutine。呃你可以启动它们但它们不会运行。所以那段代码在启动时死锁了。 Mat Ryer: 哇这真是个棘手的问题。 Johnny Boursiquot: 从未听说过这个问题。这……哇。这真是太惊人了。 Roberto Clapis: “惊人”是个说法没错……[笑声] Mat Ryer: 但我的意思是不要使用init函数……如果我们不使用init我们可能就能避免类似的问题。 Johnny Boursiquot: 同意我完全同意。[笑声] Roberto Clapis: 是的不要使用init函数。我同意。 Johnny Boursiquot: 继续吧JBD。 Jaana Dogan: 其实有时候我确实需要并行处理但并不是那种并发的需求。我只是想把一个goroutine锁定在某个处理器上然后我希望能够在不使用内部锁的情况下访问所有东西。Go语言没有给我这种精确的控制权这有点有趣。Go语言提供了很多和并发相关的特性但如果我只是想把一些工作分布到不同的处理器上而且我已经知道数据的亲和性是什么样的我却做不到因为Go并没有提供这样的能力。 并行和并发是不同的问题它们带来的优势也不同。我觉得完全没有方法去调整这些东西这有时让人有点不爽。我觉得这对普通用户来说可能并不是什么大问题但有时你会觉得自己有点被限制住了。 Mat Ryer: 是啊我的经验主要是构建很多Web项目像是Web API、网站、博客之类的东西……[笑声] 所以我不需要说“嘿我想让这个goroutine在这个核心上运行别让它跑到别的地方去。” 对我来说只要它们完成任务就行了比如渲染一些页面内容。但确实语言设计上总是有这些取舍而你需要做出判断决定这些取舍是否值得这其实也就是你对编程语言的判断。 Jaana Dogan: 你可以想象一下如果我们有那种精确的控制权那会变得非常复杂尤其是如果库开始调整这些参数之类的……所以我完全能够理解提供一个简单的API界面像是只提供goroutines和一些同步机制而不提供其他复杂的东西对整个社区其实是有益的。 这也从某种程度上给社区和用户群体划定了界限基于语言所提供的功能……所以你可能不会选择去做某些任务因为你知道语言没有提供那种功能。如果这种需求在未来变得至关重要我们可以重新考虑但也许这就是事情的运作方式。我也不确定。 Roberto Clapis: 去年我在Go Nuts上关注了一个非常长的讨论某个时刻有人——我记得是Ian Taylor——建议使用Unix包来调用set affinity把goroutine锁定到特定的处理器。我见过一些代码使用这种方式调用Unix的系统调用——当然这不是跨平台的虽然我们很喜欢Go的跨平台特性但如果你深入到这样的层次你可能还是要像在C语言中那样去做系统调用。 Jaana Dogan: 是的我就是这么做的。我锁定我的OS线程然后调用set affinity或者类似NUMA这种东西。不幸的是…… 译者注: 更多看参考 runtime包 LockOSThread Mat Ryer: 这应该成为一个演讲主题啊我们需要知道你为什么这么做以及怎么做的。这才是我们想了解的。 Jaana Dogan: 我的工作需要这么做因为有一些基准测试之类的东西所以这基本上是一个全职项目。这不是某种生产环境的服务之类的……但你知道这些工具是可用的没错。 Mat Ryer: 有意思。 Johnny Boursiquot: 你可能干脆直接用C语言写算了…… Roberto Clapis: 我开玩笑的啦。[笑声] Jaana Dogan: 我的生活就是个笑话老兄……[笑声] 我喜欢活在边缘。 Johnny Boursiquot: 流血不止的那种。 Mat Ryer: 还有一个小技巧我发现非常有用我试着描述一下……可能不是最容易解释的东西……有时候你想处理很多工作有很多任务要完成但你知道你一次只能处理五个。你希望它尽可能快地运行但同时只处理五个任务。在Go中实现这个的一个简单方法是使用一个带缓冲的通道缓冲区的大小就是你允许同时运行的goroutines的数量。然后每个goroutine尝试向这个通道写入一些东西。如果成功写入它就会继续执行它的任务。 当然前五个没问题它们可以写入通道。第六个goroutine到来时通道已经满了因为前面的goroutines占据了所有的空间所以这个goroutine就得等待直到通道中有空间释放出来。任务完成后通道中的东西会被释放就像互斥锁一样你在锁之后会延迟解锁。在这里你会写入通道然后在defer语句中读取通道释放出一个空间让另一个goroutine可以运行。 如果你了解通道的基本语义并知道如何在Go中使用它们这个模式就很容易理解……但实际上它非常强大尤其是因为缓冲区的大小是可配置的你甚至可以让程序的用户通过一个标志来指定你希望同时运行多少个任务。 当然还有另一种方式你可以启动一组固定数量的goroutines让它们从一个队列中取任务……但我觉得这种方式有点复杂因为你还需要有另一个goroutine来以某种方式填充任务队列这感觉有点奇怪。但这是我发现的一个比较喜欢的小模式。 Roberto Clapis: 我非常喜欢这个模式我也非常喜欢Go语言的简洁性使得你只用三行代码就能实现一个信号量。你刚才描述的其实就是一个信号量而通道是一个更高级的原语它允许你实现任何你想要的东西。即使你需要一个带trylock方法的互斥锁因为你想尝试获取锁如果获取不到就稍后再试……你可以用通道配合select和一个空的default块来实现。通道比互斥锁更具表现力。 Mat Ryer: 是的你还提醒了我在time包中有个time.After的函数它返回一个通道在指定的时间之后向通道发送当前时间……所以你可以在select块中使用它比如说“我们要等待这个goroutine完成。如果它在一秒钟内没有完成我们就执行另一个case。” 你可能会更新统计数据或者展示一些信息给用户这样每一秒用户都会看到更新。一旦任务完成当然会触发另一个case它就会执行其他任务。 time.After还可以用在测试代码中非常适合表达超时如果你在等待某个测试完成……在测试中加入一些超时机制也很实用。 Roberto Clapis: 我对time包有过不好的体验。[笑声] Johnny Boursiquot: 总体上吗[笑] Roberto Clapis: 是的。 Mat Ryer: 听起来像是一场独角戏主演是Roberto Clapis……[笑声] Roberto Clapis: 我在time包里遇到麻烦了…… Johnny Boursiquot: “时间包独白”…… Mat Ryer: 没错正是。 Roberto Clapis: 我真的为此画了一张流程图试图搞清楚我可以在哪些情况下调用哪些方法。比如停止ticker后你是否需要清空它如果需要的话该怎么做……这些事情。所以这是个不错的包但使用时要小心。 Mat Ryer: 这是很明智的建议……非常明智的建议。这也自然引出了我们今天节目的结束。非常感谢我们的嘉宾和共同主持Johnny Boursiquot, Jaana B. Dogan 和 Roberto Clapis。很高兴和你们在一起谢谢。 Roberto Clapis: 谢谢你 Mat Ryer: 下次见Roberto现在不是你说话的时候……我在做结束语呢。[笑声] 我们下次见在Go Time节目上
http://www.w-s-a.com/news/42113/

相关文章:

  • 网站 常见推广js代码放wordpress哪里
  • 靖江网站开发徐州住房和城乡建设局网站
  • 南宁网站建设公司如何为老板打造网站赚钱的wordpress optimizer
  • 做微商好还是开网站好网站网络推广
  • 网站建设岗位所需技能泊头网站优化
  • 企业网站建设是什么网络营销岗位介绍
  • 网站做cdn怎么弄昆明网站seo报价
  • 拖拽网站如何建立微网站
  • 网站网站做代理微信群卖房卡南宁建站模板大全
  • 网络公司怎么优化网站百度快速排名技术培训教程
  • 建e室内设计网 周婷站长工具seo综合查询源码
  • 塔式服务器主机建网站定制美瞳网站建设
  • 网站是先解析后备案吗永久免费网站模板
  • wordpress站点演示php根据ip 跳转网站
  • 东莞市凤岗建设局网站网站开发有哪些职位
  • 企业网站手机版模板免费下载辣条网站建设书
  • 南昌网站建设维护vc 做网站源码
  • 网站动态logo怎么做织梦移动端网站怎么做
  • 三亚城乡建设局网站app下载安装官方网站
  • 公司被其它人拿来做网站郑州哪家做网站最好
  • 山东省建设厅官方网站抖音代运营业务介绍
  • 网站制作 牛商网wordpress商城 微信支付
  • 平面设计培训网站建文帝网站建设
  • python网站建设佛山乐从网站建设
  • 网站 免费 托管运营app软件大全
  • 爱网站找不到了网站设计制作要交印花税
  • 分销平台是什么意思网站如何从行为数据进行优化
  • 做网站公司职务做民俗酒店到哪些网站推荐
  • 从0到建网站wordpress导航主题模板下载地址
  • 以3d全景做的网站统计网站的代码