karlgoethebier has asked for the wisdom of the Perl Monks concerning the following question:

Apropos fun. Consider this:

package main import ( "C" "fmt" "os" ) func main() {} //export Yaph func Yaph() { fmt.Println("Every day I get in the queue") fmt.Println("To get on the bus that takes me to you") } //export Acme func Acme() { go func() { fmt.Println("I guess i'm a closure") fmt.Println (os.Getpid()) }() }

Compile it:

go build -o yaph.so -buildmode=c-shared

Run it:

#!/usr/bin/env perl use strict; use warnings; use FFI::Platypus; use feature qw ( say ); say qq($0 $$); my $ffi = FFI::Platypus->new( api => 1 ); $ffi->lib('./yaph.so'); $ffi->attach(Yaph => []); $ffi->attach(Acme => []); Yaph(); Acme() for 1..3; END { say qq(Too much, Magic Bus!) for 1 .. 3; } __END__

Result:

./run.pl 12350 Every day I get in the queue To get on the bus that takes me to you I guess i'm a closure 12350 I guess i'm a closure 12350 Too much, Magic Bus! I guess i'm a closure Too much, Magic Bus! 12350 Too much, Magic Bus!

And i guess that there are many TMTOWTDI's.

Thanks for any hint and advice.

Best regards, Karl

«The Crux of the Biscuit is the Apostrophe»

Replies are listed 'Best First'.
Re: Is this a way to Go Perl #1
by Fletch (Bishop) on Aug 03, 2021 at 14:21 UTC

    Disclaimer go noob (haven't really done much with the concurrency stuff) and the question's a little vague but presuming your question is about the interleaving of the output at the end . . .

    You've started an "anonymous goroutine" in Acme. This runs asynchronously in another thread in the go runtime, but your main thread of execution returns back to perl immediately. Your loop starts two more of these async threads and then, having no more code in perl, the perl runtime goes on to start the END block code running.

    Unfortunately I'm not up enough to tell you the right way to synchronize this but I think the right thing would be to have a channel which things would flag that they've completed. You'd want something (go-side) to await on that channel (those channels?) to tell that the async bits had finished.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      «…the right way to synchronize…»

      Channels. See the next chapter 🤪😎

      «The Crux of the Biscuit is the Apostrophe»

Re: Is this a way to Go Perl #1
by kikuchiyo (Hermit) on Aug 03, 2021 at 15:11 UTC

    Frankly, I'm surprised that this much works this well.

    As for the synchronization issue, the canonical way to solve it in Go is with a sync.WaitGroup, like this:

    package main import ( "C" "fmt" "os" "sync" ) func main() {} var wg sync.WaitGroup //export Yaph func Yaph() { fmt.Println("Every day I get in the queue") fmt.Println("To get on the bus that takes me to you") } //export Acme func Acme() { wg.Add(1) go func() { defer wg.Done() fmt.Println("I guess i'm a closure") fmt.Println (os.Getpid()) }() } //export Wait func Wait() { wg.Wait() fmt.Println("All async goroutines have terminated") }

    Then, in the calling perl code:

    #!/usr/bin/env perl use strict; use warnings; use FFI::Platypus; use feature qw ( say ); say qq($0 $$); my $ffi = FFI::Platypus->new( api => 1 ); $ffi->lib('./yaph.so'); $ffi->attach(Yaph => []); $ffi->attach(Acme => []); $ffi->attach(Wait => []); Yaph(); Acme() for 1..300; Wait(); END { say qq(Too much, Magic Bus!) for 1 .. 3; } __END__

    This has the disadvantage of relying on a global variable on the Go side. I don't see any way of returning a waitgroup variable created on the fly from an exported Go function to be encapsulated into a Perl variable, then feed it back into a second Go function. Nor do I see any other way to influence the Go runtime from the Perl side.

    Still, it's an impressive demonstration. Real multithreading in a Perl program! :P

      «… Still, it's an impressive demonstration. Real multithreading in a Perl program!»

      Sure. This was the basic idea. And I wonder also why it works. A lot of stuff for further exploration. Best regards, Karl

      «The Crux of the Biscuit is the Apostrophe»

      «…the canonical way…»

      I’m not so sure about this.

      Please see here for a discussion about this issue.

      It’s answer #4.

      And BTW: In the example ibidem there is this  words := []string{"foo", "bar", "baz"} construct. Do you have any idea how to pass this as a param from Perl to the exported function?

      Best regards, Karl

      «The Crux of the Biscuit is the Apostrophe»

        I’m not so sure about this.

        And I'm quite sure about it :) -- that for this specific use case, where you start a bunch of goroutines, then wait until all of them are finished, sync.WaitGroup is the canonical way. To quote from the accepted answer of your StackOverflow link, "What is idiomatic in Go is to use the simplest and easiest to understand solution: here, the WaitGroup convey both the meaning (your main function is Waiting for workers to be done) and the mechanic (the workers notify when they are Done)."

        Other use cases may necessitate other solutions, but even then, inventing your own clever way of synchronizing between goroutines with a spaghetti of channels is not advisable. A few standard, idiomatic patterns have emerged by now, it's better to stick to those.

        By the way, I've thought a bit more about the potential uses of this go-perl hybrid, and I had to come to the sad conclusion that there aren't many. Unfortunately the interface between Perl and Go code has to go through two different interfaces (FFI and cgo), each of which brings its own set of compromises and limitations.

        For example, even though Go supports returning multiple values from functions, as does Perl, you can't use this, because the interface between the two uses C functions, which are limited to single returns. Even those are further constrained: you can't return Go pointers from a Go function, nor anything that contains a Go pointer. You have to use clumsy workarounds like go-pointer to get around this limitation. Passing any structured or array data into Go from C/Perl is similarly hard. (See also https://eli.thegreenplace.net/2019/passing-callbacks-and-pointers-to-cgo/)

        Exploiting Go's concurrency support is also a non-starter: even though it is possible to embed a Perl interpreter in Go (see Campher), and thus, in theory, to pass Perl coderefs to Go code and execute them there, you must take care to never use the Perl interpreter concurrently from different goroutines. So you'd have to run separate Perl interpreters in each goroutine you'd use for evaluating perl code, which is just like Perl's native ithreads model, except slower and even less robust.

        And BTW: In the example ibidem there is this words := []string{"foo", "bar", "baz"} construct. Do you have any idea how to pass this as a param from Perl to the exported function?

        Unfortunately, no. I've found seemingly relevant explanations (#1, #2), but I haven't tried to make it work myself.
      «…relying on a global variable on the Go side…»

      Sure. But everything has it’s price. What if we use MCE and MCE::Shared? The shared variable where we write the output to is a global - less or more and however we set it’s scope. And regardless if we fork or spawn some threads: We rely on a global. If I’m not totally wrong 😑

      «The Crux of the Biscuit is the Apostrophe»

Re: Is this a way to Go Perl #1
by Anonymous Monk on Aug 03, 2021 at 00:19 UTC
    What, exactly, is your question?