7 ms·
This all makes sense but > Green threads are different. The memory of a green thread is allocated on the heap. But all of this comes with a cost: As they aren'
by cube2222 2y ago
This all makes sense but
> Green threads are different. The memory of a green thread is allocated on the heap. But all of this comes with a cost: As they aren't managed by the OS, they can't take advantage of multiple cores inherently. But for I/O-bound operations, they are a good fit.
this is clearly not true? Am I missing some nuance here, as I'm sure the author knows what they're talking about?
Green threads can totally use a multi-threaded runtime, like e.g. Go does, and it works just fine. The main hurdle with them is arguably FFI.
- DougBTX 2y agoThe “inherently” means not by default, i.e., the runtime has to support moving green threads between OS threads itself.
- cube2222 2y agoAh, that makes sense, thanks!
- 01HNNWZ0MV43FF 2y agoThat's not how I use "inherent", maybe they should just say "default" then? But that's like... C is also single threaded by default, what isn't?
- jayd16 2y agoThey will never be transparently/fundamentalally managed by the OS alone. The runtime will need to determine how to juggle green threads across multiple OS threads. In that way, this mapping is not inherent. It can be designed around but that itself is a runtime design decision and I would not say it's akin to default vs custom.
- recursive 2y agoWith respect, it's not particularly relevant how you use "inherent". It's a standard usage. Rather than asking the whole rest of the world to change, you should probably learn the definition.
- layer8 2y ago“Inherently” means “intrinsically”, meaning it’s a characteristic that can’t be changed without changing the nature of the thing. It doesn’t mean “by default”.
- louthy 2y agoPresumably, it just means there needs to be explicit forking of the green thread for cpu bound operations, otherwise everything will run synchronously (because there’s no point where the green thread is paused to wait for an IO IRQ). That is unless your compiler or JIT injects occasional yields into your synchronous code!
- Rohansi 2y agoAnd that wouldn't be great for performance.
- 01HNNWZ0MV43FF 2y agoThe overhead for epoch stopping like wasm uses can be something like 1%. I did a synthetic test with native code once because I was curious. I think Go also injects yields into its generated code for go routine scheduling
- adgjlsfhk1 2y agoJits already have to do this for GC so it's actually free
- bilekas 2y agoThere are nuances with multi-threading in C#. I don't agree with OP about I/O-bound ops, I think if you're looking to green threading, you've taken a wrong approach. > [0] the Task.Runmethod offloads the provided action to the thread pool, and the await keyword yields control back to the caller until the task completes. All async code must be in an async call stack, virtual threads are 100% transparent because its the runtime scheduling them so you get a but more control than relying on the yeild of dotnet at least as I see it. Again I don't see the huge demand for it personally, but I barely touch dotnet too often so take this with a grain of salt. [0] https://stackify.com/c-threading-and-multithreading-a-guide-with-examples/ https://stackify.com/c-threading-and-multithreading-a-guide-...
- arghwhat 2y ago> I don't agree with OP about I/O-bound ops, I think if you're looking to green threading, you've taken a wrong It depends in the implementation. In Go for example, all I/O is async and suspend your green thread, replacing it with another runnable green thread. This works the same as if you managed an event loop on your own for the purpose of I/O, which is the best way to handle I/O outside for regular user space code. It’s just automatic with your code resembling a simple, blocking scenario. OPs note on threading would be C# or runtime specific - green threads have no problem with parallelism, with runtimes commonly having a thread per core (or more) and having them all run green threads in parallel.
- neonsunset 2y agoWhat this likely means is for you to take advantage of the underlying runtime multiplexing green threads over multiple physical ones running on multiple cores, you need to explicitly fork the execution flow. This could be as simple as a web server firing off a new green thread or a goroutine for an incoming request, or as contrived as doing so manually within a function scope. In practice, there really is not much difference with async/await. "Green threads" is a combination of implementation details and a subset of what async/await abstractions achieve. Effectively, Goroutines are in many ways similar to C# Task<T>s. The difference is that in Go you are expected to explicitly send the result via a channel or some other data structure and then synchronize the completion of the execution, where-as with tasks you simply await that. There could be an argument made about preference of implicit suspend (Go, Java, BEAM family) over explicit suspend (C#/F#, Rust, JS, Python, C++ co_await, Swift), but for practical purposes invoking a function with 'go' keyword in Golang is very similar to firing off a synchronous method with Task.Run in C#, or calling an asynchronous method (with sufficiently short body before first yield) and not immediately awaiting it. As I usually post it on HN, tasks make the following patterns trivial: using var http = new HttpClient { BaseAddress = new("https://news.ycombinator.com/") }; // not immediately awaited requests are executed in parallel var frontPage = http.GetStringAsync("news?p=1"); var secondPage = http.GetStringAsync("news?p=2"); Console.WriteLine($"{await frontPage}\n\n{await secondPage}");
- spinningslate 2y ago> The difference is that in Go you are expected to explicitly send the result via a channel or some other data structure and then synchronize the completion of the execution, where-as with tasks you simply await that. That may be be the case in Go but it's not an inherent property of green threads. See, for example, Gleam Tasks [0] which are based on green threads and provide the syntatic convenience of being able to await the result rather than receiving a message: let task = task.async(fn() { do_some_work() }) let value = do_some_other_work() value + task.await(task, 100) They do so without the disadvantage of bifurcating the code base into sync and async functions. [0] https://hexdocs.pm/gleam_otp/gleam/otp/task.html https://hexdocs.pm/gleam_otp/gleam/otp/task.html
- pron 2y agoThe efficiency and complexity of user mode threads heavily depend on constraints imposed by the particular language. E.g. if the language supports pointers into the stack, user mode threads would be less efficient; if the language is largely dependent on manual memory management -- user mode threads would be more expensive; if the language already has some other concurrency primitives (like async/await) -- user mode threads will be more expensive (although in this case in terms of complexity rather than runtime efficiency). Because Java exposes relatively little of its implementation details, we've been able to implement efficient user mode threads even without any FFI overhead.
- andygocke 2y agoWell, any additional FFI overhead, right? The cost for exposing very little tends to be that marshaling costs more due to the requirement that values be copied between domains rather than shared.
- pron 2y agoThat's a matter of perspective. Calling a C function in a shared library (dll, so) from Java using the new FFM API has the same overhead as calling such a function from C++ (although the overhead is higher if the called function upcalls into Java again, though that is relatively rare, or if the function blocks, only that makes the additional overhead negligible). But the FFM API does not directly expose Java objects to native code at all, although it does allow Java code to access and mutate "off-heap" native memory (C data) from Java code as efficiently as accessing and mutating Java heap memory. So if your goal is to expose Java objects to native code, then yes, that would require marshalling (although ideally you should do the opposite and expose native memory to Java code as trhough a Java interface, which would have no overhead). However, relying on FFI in Java is far less common than in Python, Rust, or even C# or Go, and in the rare cases it's done it's easy to do it cheaply as I described. So I guess it's true to say that if you wanted FFI to work in the same manner it is employed in those other languages then yes, it would be more expensive as it would require marshalling, but that's just not the case in Java given the combination of Java's performance and size of its ecosystem of libraries. Languages with worse performance or with smaller ecosystems do need to rely much more heavily on FFI and so they often choose to sacrifice the flexibility of their implementation in favour of a more direct flavour of FFI.
- andygocke 2y agoI'm not sure that the original description is precisely correct, but yours isn't correct either. Basically, you can't treat green threads just like "a multi-threaded runtime" and have it just work. That is, a 1:1 mapping between green threads and OS threads is just OS threads. So fundamentally if you bounce your green stacks off of the actual stack they're going to need to go somewhere... and that place must be the heap. There are pluses and minuses to this implementation, but the biggest minus is that it makes FFI very complicated. C# has an extremely rich native-interop history (having historically been used to integrate closely with Windows C++ applications) and therefore this approach raised some serious challenges. In some sense, async is the cost for clean interop with the C/system ABI. Transition across OS threads requires something like async.
- cube2222 2y agoI meant that you can have a multi-threaded runtime that will be executing your green threads in a multi-threaded fashion. Like in Go you have (by default) as many worker OS threads as CPUs, and the Go runtime will take care of scheduling your green threads on those worker OS threads (+ create threads as needed for blocking syscalls if I remember correctly, but that's getting way to deep into the details). And this will, in fact, "just work" from the user's perspective. And yes, as both you said, and I said at the end of my previous comment, the main hurdle of green threads imo is FFI, but it's not what the article mentions, which is what surprised me.
- andygocke 2y agoAh, I see. You were saying that green threads can usually be scheduled on multiple os threads and take advantage of parallelism. Yup, I agree. Apologies for the confusion.