草庐IT

go - 带有 channel 的Goroutine的行为

coder 2024-07-13 原文

下面给出的代码输出有些混乱,请帮助我理解通道和goroutine的行为以及如何
执行实际上发生了。

我试图理解程序的流程,但是执行了“调用goroutine”之后的语句,即使goroutine被调用了,
稍后执行goroutines中的语句,

在第二次“调用goroutine”时,行为不同,并且打印/程序流程的顺序发生了变化。

以下是代码:

    package main

    import "fmt"

    func main() {
        fmt.Println("1")
        done := make(chan string)
        go test(done)
        fmt.Println("7")
        fmt.Println(<-done)
        fmt.Println("8")
        fmt.Println(<-done)
        fmt.Println("9")
        fmt.Println(<-done)
    }
    func test(done chan string) {
        fmt.Println("2")
        done <- "3"
        done <- "10"
        fmt.Println("4")
        done <- "5"
       fmt.Println("6")
    }

上面代码的结果:
1
7
2
3
8
10
9
4
6
5

请帮助我了解为什么以及如何得出此结果。

最佳答案

欢迎使用堆栈溢出。希望我能帮助您更好地了解频道和goroutines。

概念1:Channels

将通道可视化为一条管,数据在一端进入另一端。输入的第一个数据是从另一侧输出的第一个数据。有缓冲通道和非缓冲通道,但是对于您的示例,您只需要了解默认通道即可。未缓冲的通道一次只允许一个值在通道中。

写入无缓冲通道

看起来像这样的代码将数据写入通道的一端。

ch <- value

现在,该代码实际上等待执行完成,直到有人从通道读取值为止。一个无缓冲的通道一次只允许一个值位于其中,并且直到被读取后才继续执行。稍后我们将看到这如何影响代码执行的顺序。

从无缓冲通道读取

要从未缓冲的通道读取(可视化从通道中取出一个值),执行此操作的代码如下所示:
[value :=] <-ch

阅读代码文档时,方括号中的内容表示其中的内容是可选的。上面,没有[value:=],您只会从通道中取出一个值,并且不会将其用于任何用途。

现在,当通道中有一个值时,此代码有两个副作用。第一,它以我们现在所使用的例程从通道中读取值,然后继续该值。它的另一个效果是允许将值放入通道的goroutine继续。这是理解示例程序所必需的关键部分。

如果通道中还没有值,它将继续运行,然后等待将值写入通道。换句话说,线程阻塞直到通道具有要读取的值。

概念2:Goroutines

使用goroutine,您的代码可以继续同时执行两个代码。这可用于允许您的代码更快地执行,或同时遇到多个问题(例如,一个服务器上有多个用户同时从中加载页面)。

当您尝试弄清同时执行多个例程时执行代码的顺序时,就会出现问题。这是一个很好的问题,其他人正确地指出了这一点。当您生成两个goroutine时,执行代码行的顺序是任意的。

下面带有goroutine的代码可能会首先打印executing a()end main()。这是由于以下事实:产生gorouting意味着同时发生两个并发的执行流(线程)。在这种情况下,一个线程停留在main()中,另一个线程开始执行a()中的第一行。运行时如何决定选择先运行是任意的。
func main() {
    fmt.Println("start main()")
    go a()
    fmt.Println("end main()")
}

func a() {
    fmt.Println("executing a()")
}

Goroutines +频道

现在,让我们使用一个通道来控制何时执行获取操作的顺序。
现在唯一的区别是,我们创建一个通道,将其传递到goroutine中,并等待其值被写入,然后再继续执行main。从较早的时候开始,我们就讨论了如何从通道读取值的例程需要等到通道中有值才能继续。由于executing a()总是在写入通道之前打印,因此我们将始终等待读取放入通道中的值,直到executing a()已打印为止。由于我们在打印end main()之前从通道读取(这是在写入通道之后发生的),因此executing a()将始终在end main()之前打印。我制作了this playground,因此您可以自己运行它。
func main() {
    fmt.Println("start main()")
    ch := make(chan int)
    go a(ch)
    <-ch
    fmt.Println("end main()")
}

func a(ch chan int) {
    fmt.Println("executing a()")
    ch <- 0
}

你的例子

我认为此时您可以弄清楚什么时候会发生什么,以及可能以不同的顺序发生什么。我脑海中的第一次尝试是错误的(请参阅编辑历史记录)。你必须要小心!在编辑时,我不会给出正确的答案,因为我意识到这可能是一项家庭作业。

编辑:有关<-done的更多语义

在我的第一次浏览中,我忘记提到fmt.Println(<-done)在概念上与以下内容相同。
value := <-done
fmt.Println(value)

这很重要,因为它可以帮助您看到当main()线程从done通道读取时,它不会同时打印它。这是运行时的两个单独步骤。

我希望这有帮助。如果您有任何问题,请告诉我。

关于go - 带有 channel 的Goroutine的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54403750/

有关go - 带有 channel 的Goroutine的行为的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby - Ruby gsub 替换中的行为不一致? - 2

    两个gsub产生不同的结果。谁能解释一下为什么?代码也可在https://gist.github.com/franklsf95/6c0f8938f28706b5644d获得.ver=9999str="\tCFBundleDevelopmentRegion\n\ten\n\tCFBundleVersion\n\t0.1.190\n\tAppID\n\t000000000000000"putsstr.gsub/(CFBundleVersion\n\t.*\.).*()/,"#{$1}#{ver}#{$2}"puts'--------'putsstr.gsub/(CFBundleVersio

  3. ruby-on-rails - 带有 Zeus 的 RSpec 3.1,我应该在 spec_helper 中要求 'rspec/rails' 吗? - 2

    使用rspec-rails3.0+,测试设置分为spec_helper和rails_helper我注意到生成的spec_helper不需要'rspec/rails'。这会导致zeus崩溃:spec_helper.rb:5:in`':undefinedmethod`configure'forRSpec:Module(NoMethodError)对thisissue最常见的回应是需要'rspec/rails'。但这是否会破坏仅使用spec_helper拆分rails规范和PORO规范的全部目的?或者这无关紧要,因为Zeus无论如何都会预加载Rails?我应该在我的spec_helper中做

  4. ruby-on-rails - Ruby 中意外的大小写行为 - 2

    我在一段非常简单的代码(如我所想)中得到了一个错误的值:org=4caseorgwhenorg=4val='H'endputsval=>nil请不要生气,我希望我错过了一些非常明显的东西,但我真的想不通。谢谢。 最佳答案 这是典型的Ruby错误。case有两种被调用的方法,一种是你传递一个东西作为分支的基础,另一种是你不传递的东西。如果您确实在case中指定了一个表达式语句然后评估所有其他条件并与===进行比较.在这种情况下org评估为false和org===false显然不是真的。所有其他情况也是如此,它们要么是真的,要么是假的。

  5. ruby - 使对象的行为类似于 ruby​​ 中并行分配的数组 - 2

    假设您在Ruby中执行此操作:ar=[1,2]x,y=ar然后,x==1和y==2。是否有一种方法可以在我自己的类中定义,从而产生相同的效果?例如rb=AllYourCode.newx,y=rb到目前为止,对于这样的赋值,我所能做的就是使x==rb和y=nil。Python有这样一个特性:>>>classFoo:...def__iter__(self):...returniter([1,2])...>>>x,y=Foo()>>>x1>>>y2 最佳答案 是的。定义#to_ary。这将使您的对象被视为要分配的数组。irb>o=Obje

  6. Ruby:如何使用带有散列的 'send' 方法调用方法? - 2

    假设我有一个类A,里面有一些方法。假设stringmethodName是这些方法之一,我已经知道我想给它什么参数。它们在散列中{'param1'=>value1,'param2'=>value2}所以我有:params={'param1'=>value1,'param2'=>value2}a=A.new()a.send(methodName,value1,value2)#callmethodnamewithbothparams我希望能够通过传递我的哈希以某种方式调用该方法。这可能吗? 最佳答案 确保methodName是一个符号,而

  7. ruby-on-rails - 带有 Pry 的 Rails 控制台 - 2

    当我进入Rails控制台时,我已将pry设置为加载代替irb。我找不到该页面或不记得如何将其恢复为默认行为,因为它似乎干扰了我的Rubymine调试器。有什么建议吗? 最佳答案 我刚发现问题,pry-railsgem。忘记了它的目的是让“railsconsole”打开pry。 关于ruby-on-rails-带有Pry的Rails控制台,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/question

  8. 带有 attr_accessor 的类上的 Ruby instance_eval - 2

    我了解instance_eval和class_eval之间的基本区别。我在玩弄时发现的是一些涉及attr_accessor的奇怪东西。这是一个例子:A=Class.newA.class_eval{attr_accessor:x}a=A.newa.x="x"a.x=>"x"#...expectedA.instance_eval{attr_accessor:y}A.y="y"=>NoMethodError:undefinedmethod`y='forA:Classa.y="y"=>"y"#WHATTT?这是怎么回事:instance_eval没有访问我们的A类(对象)然后它实际上将它添加到

  9. ruby - 了解在 Ruby 中与 lambda 一起使用的 inject 行为 - 2

    我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject

  10. ruby-on-rails - Rails 渲染带有驼峰命名法的 json 对象 - 2

    我在一个简单的RailsAPI中有以下Controller代码:classApi::V1::AccountsControllerehead:not_foundendendend问题在于,生成的json具有以下格式:{id:2,name:'Simpleaccount',cash_flows:[{id:1,amount:34.3,description:'simpledescription'},{id:2,amount:1.12,description:'otherdescription'}]}我需要我生成的json是camelCase('cashFlows'而不是'cash_flows'

随机推荐