12 ms·
Go's Concurrency Examples in Java 19
- rad_gruchalski 4y agotime.Sleep(100 * time.Millisecond) could also be: <-time.After(100 * time.Millisecond) so that an imaginary timeout can be easily handled with a select.
- emccue 4y agoTrue, but I didn't write any of the Go here. Somewhat out of my wheelhouse
- nahuel0x 4y agoBe careful with time.After, if you select on time.After and another channel c, and c breaks the select, then the timer will not be deallocated until it expires. So if the select is on a loop you can easily leak memory. Another of the Go hidden gotchas.
- marginalia_nu 4y agoWhile there are use cases where lightweight threads are a benefit, I think a web crawler is pretty much the ideal case for async/await, given you are almost exclusively waiting. Although I must admit I don't really understand this example, why is the fetching being parallellized, like you typically want to add sleeps, even with a single thread you've got yourself a gatling gun that can DoS a website if you aren't careful. There's also some optimizations that could be made. Instead of having synchronized (seen) { if (!seen.contains(u)) { seen.add(u); // ... } } you can get rid of the synchronization with // seen = ConcurrentHashMap.newKeySet() if (seen.add(u)) { // ... }
- vips7L 4y agoI was surprised by the lack of ConcurrentHashMap. Does Go not have an equivalent?
- racingmars 4y agoGo does have a concurrent map in the standard library, but the documentation recommends not using it unless you have specific access patterns that it's optimized for: https://pkg.go.dev/sync#Map https://pkg.go.dev/sync#Map You lose type safety when using it. I'm guessing with generics in 1.18 now, we'll see an updated generic version of sync.Map whenever a bunch of new collection types are added to the standard library that take advantage of generics.
- EdwardDiego 4y agoYeah, I'm looking forward to Go's stdlib evolving useful collections now that generics exist. Like maybe a set. That isn't a map[T]interface{} and can do things like unions and intersections so I don't have to implement it with for loops.
- masklinn 4y ago> That isn't a map[T]interface{} FWIW while map[T]interface{} is somewhat more convenient to use, for efficiency reasons you should prefer map[T]struct{}: interface{} being a fat pointer is takes 16 bytes * the capacity of the map/set. struct{} is a zst, so takes 0. It also signals much more clearly that it's a map to nothing, which is a set, a map[T]interface{} could be an actual map to a bunch of random things.
- Cthulhu_ 4y agoWhile you're not wrong, and that is how you implement a set in Go, it's still a workaround over a standard library Set type unfortunately. But, with Go's generics, I hope they will add new collection types to the standard library.
- assbuttbuttass 4y agoIt's actually much more common to use map[T]bool, because it allows easy membership testing just with if m[x] { ... }
- emccue 4y agoYeah - ConcurrentHashMap makes the most sense. I left it off just because I thought it would be easier to see the parallel with the Go code.
- ibraheemdev 4y ago> There is no way for your operating system to know exactly how much stack space a thread will need so it allocates an amount on the order of around a megabyte. You only have around a bakers dozen gigabytes of RAM, so you can only make give or take 10,000 threads. That's not true at all. The megabyte(s) of stack space is virtual, spawning a thread only requires a few kilobytes of memory initially, and is a lot cheaper than many realize.
- taspeotis 4y agoIt's also not true when ... you tell it how much you need. Windows lets you specify a stack size [1]. [1] https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread https://docs.microsoft.com/en-us/windows/win32/api/processth...
- maeln 4y agoFun fact, you can declare a stack size at compile time (something like `cl ... -stack:0x5000000,0x5000000 ...`) which let you create insane stack size so that you can fit your all your software in the stack. Useful when you don't want to had an allocator when making tiny tiny executable files :D
- SemanticStrengh 4y agoThe main cost of spawning a thread is simply the time it takes to spawn.
- latch 4y agoWhat are you trying to say? This sounds like a tautology to me.
- MikeTheGreat 4y agoI think they meant that the main cost of a new thread that you'd like to create is the time it takes to spawn (i.e., the time it takes to set up the new thread and get it ready to run) and NOT the memory that gets allocated for it, nor the operating system overhead of keeping it around afterwards, etc, etc. (I'm not agreeing or disagreeing; I'm only trying to helpfully clarify)
- freakynit 4y agoDoes GO even makes sense after Java's project loom and graalvm?
- EdwardDiego 4y agoGraalvm takes some special effort to get things building natively and the binaries are far larger, so I'm going to go with "yeah". And I'm hardly a fan of Go.
- emccue 4y agoHypothetically there is a version of "static Java" that could start to encroach upon Go's domain. This may or may not come out of Project Leyden, but I think for the reason the other commenter gave GraalVM isn't exactly it. Still closer though, and GraalVM native images are pretty cool. https://mail.openjdk.java.net/pipermail/discuss/2020-April/005429.html https://mail.openjdk.java.net/pipermail/discuss/2020-April/0...
- klabb3 4y agoDid you see the examples? Even with the blocking threaded style the Java versions gave me nightmares. And they didn't even bring up a cancelation example (not 100% sure on that). Cancelation and subsequent graceful shutdown is one of the hardest concurrency problems that (a) you are very likely to need in practice and (b) conveniently often are left out of example code. Heck it isn't even great in Go (too explicit imo) but at least they solved it.
- emccue 4y agoCan you elaborate more? All of the parts that throw `InterruptedException` are handling the thread interrupt cancelation mechanism. There is also the example with the atomic boolean quit flag. I'm not convinced that Go has any unique mechanisms here. Just like Java you can't forcefully kill a thread without that thread's cooperation.
- mperham 4y agoGo provides a context package which allows in-flight cancellation of heavy operations spanning processes and even machines. https://pkg.go.dev/context https://pkg.go.dev/context
- sidcool 4y agoHow is this different than Kotlin coroutines? They seem to already support this feature.
- pjmlp 4y agoFirst class support on the JVM, instead of .class boilerplate emulating the feature, usable only for Kotlin.
- emccue 4y agoThe example I think is compelling is JDBC. Nearly all SQL Database access goes through it and it won't be rewritten in any practical time scale. It can't be meaningfully async without a rewrite and kotlin's coroutines can't change that. With virtual threads that synchronous api can be used at the performance level of what a hypothetical JDBC rewite would give. (Not that you want to hit your DB that much, but you know what I mean. You don't need to specially wrap or bundle apis to get into an async call juggle.)
- orthogonal-wren 4y agoThere are already some efforts in that direction. You can check out r2dbc and the many vert.x sql clients. While they're not really jdbc, they're fully async like you described.
- caladin 4y agoI was investigating this lately, and as you say, they are "efforts", and while laudable, I wouldn't go so far as to call them solutions. There are open issues/caveats as a result of the async model. I do think there is something great about being able to continue to leverage existing JDBC-related tooling, given that it already exists.
- funcDropShadow 4y agoAnd the importance of this first class support cannot be overstated. It means: - meaningful complete stacktraces - debugger support - all blocking libraries get the benefits of async code automatically [1] - all monitoring features of the platform work - profilers get more useful again - less mental overhead when programming, i.e. simplicity [1]: With one caveat: Libraries which call directly into blocking native calls do not become async automatically. But they are very rare on the Java platform.
- orthogonal-wren 4y agoGood read! I just saw that in one examples you create and start a thread by invoking #startVirtualThread, and then you call start on the returned thread again.
- emccue 4y agoOof, fixed
- funcDropShadow 4y agoLoom's virtual thread are much more similar to GHC's threads than to go routines. And GHC Haskell is also able to spawn millions of threads, trivially.
- emccue 4y agoCan you elaborate more? What is the distinguishing factor?
- ziihrs 4y agoIn Go, the select branch to take is non-deterministically chosen from the set of enabled branches. For the examples I don't think it makes a difference, but in general you cannot assume that a select attempts to choose branches from top to bottom.
- Cthulhu_ 4y agoI wonder if that's on purpose, to find bugs earlier. I know that the implementation of map in Go will intentionally shuffle the keys when you try to iterate over them, so that you never accidentally rely on insertion order.
- yakubin 4y agoI've read somewhere (possibly when Go came out) that it's on purpose. For fun, Alan Cox once made a post on Google+ (RIP) showing a crappy random number generator based on Go's select.
- dgb23 4y agoIt's very much on purpose, just like default maps being unordered. People should not rely on the ordering as it breaks the semantics of these features according to Go. It fits the Go philosophy well.
- Strum355 4y agoIts non deterministic on purpose because thats how its required to be to fit the model of CSP (communicating sequential processes)
- pron 4y agoPeople sometimes respond with some puzzlement to InterruptedException, unsure what they should do. All it means is that some other thread has requested that this thread stop what it's doing. So do what you think your code should do in that situation. E.g. in the case of `say`, the most reasonable thing is to simply return. You shouldn't normally call `Thread.currentThread().interrupt()` inside a `catch (InterruptedException e)` unless, perhaps, you don't throw anything. That would prevent any blocking operation inside cleanup code from working. As a general rule, a thread's interrupt status is set or InterruptedException (or some other exception) is thrown, but not both. That is how JDK methods also do it. If they throw — they clear the interrupt status.
- emccue 4y agoAight. I only started doing this recently after this advice here https://discord.com/channels/544924237773668373/930457954346491924/963003013667127338 https://discord.com/channels/544924237773668373/930457954346... I'll clear it from the examples
- K2L8M11N2 4y agoJust FYI, that link is not publicly accessible.
- deleted 4y ago[deleted]
- xyproto 4y agoJudging by the examples, the Go code is easier to read, write and maintain.
- ivolimmen 4y agoWell as someone that has been developing software in Java for 20 years and never touched Go; it's the other way around.
- Cthulhu_ 4y agoCan you elaborate? I mean there may be some new operators and concepts in the Go versions, but they're explained succinctly in https://go.dev/tour/ https://go.dev/tour/
- kaba0 4y agoJava is a similarly small language, with perhaps even less of a learning curve.
- doctor_eval 4y agoI developed and maintained Java software professionally for over 20 years, since 1.0, but now I work in Go. There is no way I’d willingly go back to Java, and these examples really underscore some of the reasons. I do however wonder, now, what I’m missing out on with other languages, particularly swift and rust.
- bsaul 4y agoSwift has great support for enums (and ADT in general). That's about the only thing i miss when i switch from swift to go. Generics and protocol oriented programming is nice, but the ergonomics makes it still not that enjoyable (you start banging your head against walls pretty quickly beyond the most simple examples).
- kaba0 4y agoRust has a great, Haskell-inspired way to create algebraic data structures (with the enum keyword). These same structures can be very nicely destructured and pattern matched on.
- nyanpasu64 4y agoInteresting article history! Does your site have a RSS feed to keep up with updates in a feed reader? I couldn't find one.
- matttproud 4y ago> In Go there is less noise, but also there no way to interrupt Go's time.Sleep. You can do so in one of two ways using the context API (https://go.dev/blog/context https://go.dev/blog/context) while keeping the code synchronous and idiomatic: package main import ( "context" "fmt" "time" ) func say(ctx context.Context, s string) { for i := 0; i < 5; i++ { select { case <-time.After(100 * time.Millisecond): fmt.Println(s) case <-ctx.Done(): return // Operation canceled or timed out. } } } func main() { ctx := context.Background() // Use context cancellation semantics. go say(ctx, "world") say(ctx, "hello") } If the code is reasonably performance sensitive AND there's a high degree of likelihood the context is canceled before the operation completes, you can use a time.Timer similar to what follows: for i := 0; i < 5; i++ { func(i int) { // Wrap in closure expressly to support defer statement. t := time.NewTimer(100 * time.Millisecond) defer t.Stop() select { case <-t.C: fmt.Println(s) case <-ctx.Done(): return // Operation canceled or timed out. } }(i) }
- deepsun 4y agoGo solution to TreeWalk problem is wrong, or, rather, not real-world, because it has a memory leak. You need to make sure all goroutines finish, and not stuck on reading/writing to a channel. In Kotlin coroutines you can .cancel() a coroutine, but Go doesn't allow to cancel goroutines.