Remix.run Logo
kbd 7 hours ago

One of the harms Go has done is to make people think its concurrency model is at all special. “Goroutines” are green threads and a “channel” is just a thread-safe queue, which Zig has in its stdlib https://ziglang.org/documentation/master/std/#std.Io.Queue

jerf 7 hours ago | parent | next [-]

A channel is not just a thread-safe queue. It's a thread-safe queue that can be used in a select call. Select is the distinguishing feature, not the queuing. I don't know enough Zig to know whether you can write a bit of code that says "either pull from this queue or that queue when they are ready"; if so, then yes they are an adequate replacement, if not, no they are not.

Of course even if that exact queue is not itself selectable, you can still implement a Go channel with select capabilities in Zig. I'm sure one exists somewhere already. Go doesn't get access to any magic CPU opcodes that nobody else does. And languages (or libraries in languages where that is possible) can implement more capable "select" variants than Go ships with that can select on more types of things (although not necessarily for "free", depending on exactly what is involved). But it is more than a queue, which is also why Go channel operations are a bit to the expensive side, they're implementing more functionality than a simple queue.

kbd 5 hours ago | parent | next [-]

> I don't know enough Zig to know whether you can write a bit of code that says "either pull from this queue or that queue when they are ready"; if so, then yes they are an adequate replacement, if not, no they are not.

Thanks for giving me a reason to peek into how Zig does things now.

Zig has a generic select function[1] that works with futures. As is common, Blub's language feature is Zig's comptime function. Then the io implementation has a select function[2] that "Blocks until one of the futures from the list has a result ready, such that awaiting it will not block. Returns that index." and the generic select switches on that and returns the result. Details unclear tho.

[1] https://ziglang.org/documentation/master/std/#std.Io.select

[2] https://ziglang.org/documentation/master/std/#std.Io.VTable

jerf 3 hours ago | parent | next [-]

Getting a simple future from multiple queues and then waiting for the first one is not a match for Go channel semantics. If you do a select on three channels, you will receive a result from one of them, but you don't get any future claim on the other two channels. Other goroutines could pick them up. And if another goroutine does get something from those channels, that is a guaranteed one-time communication and the original goroutine now can not get access to that value; the future does not "resolve".

Channel semantics don't match futures semantics. As the name implies, channels are streams, futures are a single future value that may or may not have resolved yet.

Again, I'm sure nothing stops Zig from implementing Go channels in half-a-dozen different ways, but it's definitely not as easy as "oh just wrap a future around the .get of a threaded queue".

By a similar argument it should be observed that channels don't naively implement futures either. It's fairly easy to make a future out of a channel and a couple of simple methods; I think I see about 1 library a month going by that "implements futures" in Go. But it's something that has to be done because channels aren't futures and futures aren't channels.

(Note that I'm not making any arguments about whether one or the other is better. I think such arguments are actually quite difficult because while both are quite different in practice, they also both fairly fully cover the solution space and it isn't clear to me there's globally an advantage to one or the other. But they are certainly different.)

kbd 2 hours ago | parent [-]

> channels aren't futures and futures aren't channels.

In my mind a queue.getOne ~= a <- on a Go channel. Idk how you wrap the getOne call in a Future to hand it to Zig's select but that seems like it would be a straightforward pattern once this is all done.

I really do appreciate you being strict about the semantics. Tbh the biggest thing I feel fuzzy on in all this is how go/zig actually go about finding the first completed future in a select, but other than that am I missing something?

https://ziglang.org/documentation/master/std/#std.Io.Queue.g...

SkiFire13 3 hours ago | parent | prev [-]

Maybe I'm missing something, but how do you get a `Future` for receiving from a channel?

Even better, how would I write my own `Future` in a way that supports this `select` and is compatible with any reasonable `Io` implementation?

jeffbee 6 hours ago | parent | prev [-]

If we're just arguing about the true nature of Scotsmen, isn't "select a channel" merely a convenience around awaiting a condition?

jerf 3 hours ago | parent | next [-]

This is not a "true Scotsman" argument. It's the distinctive characteristic of Go channels. Threaded queues where you can call ".get()" from another thread, but that operation is blocking and you can't try any other queues, then you can't write:

    select {
    case result := <-resultChan:
        // whatever
    case <-cxt.Done():
        // our context either timed out or was cancelled
    }
or any more elaborate structure.

Or, to put it a different way, when someone says "I implement Go channels in X Language" I don't look for whether they have a threaded queue but whether they have a select equivalent. Odds are that there's already a dozen "threaded queues" in X Language anyhow, but select is less common.

Again note the difference between the word "distinctive" and "unique". No individual feature of Go is unique, of course, because again, Go does not have special unique access to Go CPU opcodes that no one else can use. It's the more defining characteristic compared to the more mundane and normal threaded queue.

Of course you can implement this a number of ways. It is not equivalent to a naive condition wait, but probably with enough work you could implement them more or less with a condition, possibly with some additional compiler assistance to make it easier to use, since you'd need to be combining several together in some manner.

SkiFire13 3 hours ago | parent | prev [-]

It's more akin to awaiting *any* condition from a list.

0x696C6961 7 hours ago | parent | prev | next [-]

What other mainstream languages have pre-emptive green threads without function coloring? I can only think of Erlang.

smw 7 hours ago | parent | next [-]

I'm told modern Java (loom?) does. But I think that might be an exhaustive list, sadly.

femiagbabiaka 7 hours ago | parent | prev [-]

Maybe not mainstream, but Racket.

dlisboa 6 hours ago | parent | prev [-]

It was special. CSP wasn't anywhere near the common vocabulary back in 2009. Channels provide a different way of handling synchronization.

Everything is "just another thing" if you ignore the advantage of abstraction.