10 ms·
Swift Concurrency Roadmap
- bestinterest 6y agoCan anyone fill me in on how this compares to Java's Project Loom approach where they have said a hard no to coloring functions. What are the advantages and disadvantages of async/await vs introducing a green thread like approach for problems? Is async/await more suitable for user interfaces?
- _old_dude_ 6y agoImplementing async/await is only a compiler change, so it can be implemented rather easily on top of any languages, the compiler transforms the code to a state machine. co-routine (Java Loom's one, Go one, Scheme one, etc) requires to be able to serialize and deserialize a part of the stack (move the stack on the the heap and vice versa), so it's hard/impossible to implement with non managed runtimes which like in C or objective C. In C, you can declare an address/pointer to an address on stack (using &), but with a co-routine mechanism, the addresses of parts of the stack are not constant. If you have a managed runtime, you rewrite those kind of pointers when you copy parts of the stack back and forth.
- gpderetta 6y agoWith one stack per coroutine you do not have to move stack frames. It has different tradeoffs of course.
- ynik 6y agoasync/await is more flexible in some sense. If you run the tasks on a thread pool, it's mostly equivalent to green threads. But if you run the tasks on a single thread, you get cooperative multi-tasking. You can access mutable shared state without using locks, as long as you don't use "await" while the shared state is in an inconsistent state. For user interfaces this is huge advantage: you can run all your code on the UI thread; but the UI stays responsive while awaiting tasks. Also, here's an interesting use of async/await: software hyperthreading: https://isocpp.org/blog/2019/09/cppcon-2018-nano-coroutines-to-the-rescue-using-coroutines-ts-of-course-g https://isocpp.org/blog/2019/09/cppcon-2018-nano-coroutines-...
- pron 6y agoLoom's virtual threads allow you to do the same with a pluggable scheduler.
- jayd16 6y agoHow does UI code work in Loom if you need, say, a single render thread to issue GL commands? Wouldn't you need a native thread (making Loom moot), or is there some other trick at play?
- pjmlp 6y agoYou can still pick and choose, native threads aren't going away, just like today you can make several tasks map to a single threaded.
- jayd16 6y agoAlright. The trick is to not use Loom virtual threads. I will say Java's Loom is ruthlessly elegant in this way. But won't you run into issues where you don't want to call certain code from that UI thread? Isn't that "code coloring"?
- pjmlp 6y agoCode colouring usually means it won't even compile, like on C# async/await case. In your example, for Swing applications you should use invokeLater(Runnable r) anyway, and it doesn't matter what implements the interface.
- pron 6y agoYou would use a custom scheduler to schedule many virtual threads onto a particular native thread.
- jayd16 6y agoI don't think this is enough. You still might interweave your draw commands, causing issues.
- smasher164 6y agoasync/await is really just another formulation of linked stack frames, where control is explicitly yielded. At the end of the day, the thread stack state needs to be reified somehow, and all of these approaches are equivalent in power. The only difference is that now you have a type system that delineates between functions that can and cannot be shuffled between kernel threads. The benefits that Go (and potentially Loom) provide are with the scheduler. When Go code calls into other Go code, it's fast because preemption points can be inserted by the compiler. The goroutine is parked when it is blocked (on I/O or some foreign function), and in the slow path this logic is executed on a new kernel thread. Although the Go approach involves more overhead in the slow path, wrangling blocking code to work with the scheduler has cross-cutting implications for library design. I.e, I don't have to worry that a library I import that does file I/O will pin my goroutine to a blocked thread by using a blocking syscall.
- jayd16 6y agoAsync/await provides a mechanism for explicit context switching between threads. This is useful when you might need to run code on specific native threads or other native tasks outside the runtime. It seems like it would be much harder or impossible in languages that work to hide these details. How do you go from implicit blocking to explicit native thread usage? At least Loom seems to be keeping native threads so Java code can simply fall back to that, I guess.
- pjmlp 6y agoYou can also use native threads in Go, by locking the current goroutine thread.
- yxhuvud 6y agoOne way to solve it is to introduce a concept like a nursery (a la structured concurrency) and when setting it up you instruct it to create a new thread that is only used for those particular tasks. This of course requires a scheduler that needs to handle hierarchical control of execution rather than having an unformed mass of tasks, but nurseries provide other benefits as well.
- aardvark179 6y agoIn Loom we can make some trade offs around our knowledge of the Java stack and the standard library. 1. We know that no JVM frame contains a raw pointer to anywhere else in the stack. 2. We know which stack frames are JVM frames, and which are native frames. 3. We know that there are unlikely to be native frames in the portion of the stack between the point where we yield control and the point we want to yield to. 4. We can change the standard library to check if we are in our new lightweight thread or not and act appropriately. Knowing these we can avoid function coloring and push the complex management of green threads into the standard library, and reuse existing task scheduler code to manage these virtual threads, and we can work to make thread local variables etc work seamlessly with this new concurrency abstraction. This puts us in a very different design space. We can move a portion of the stack to the heap when a thread is unmounted, and we can change things about the GC and other internal systems to make them aware of this new thing that can be in the heap. This type of approach would be much harder in a language like swift that tends to coexist with C and other languages that use raw pointers or a non-moving GC, so I think the question is not which is the better approach but which is the better approach within your language ecosystem.
- didibus 6y agoIf Loom ever comes out :p, at this point I am starting to wonder if this is a case of "the right thing" against "worse is better" and maybe we just will never see the light of Loom.
- spullara 6y agoYou can download loom and use it right now. I am pretty sure it will one day be released.
- pron 6y agoJava is at a stage where it's clear it will continue to be a popular platform and language for decades to come. It can afford to do "the right thing" because the question it's facing is not how to attract more developers now, but how to avoid mistakes its numerous developers will pay for, and build solid foundations for many years to come. It's a common difference between young and established languages, and, naturally, Java made its own fair share of design mistakes when it was young. Of course, there are technical differences that make certain design choices easier or harder in different languages. As aardvark179 mentions above, Java doesn't have pointers into the stack, and intermediate FFI frames are extremely rare (also due to the platform's established and large ecosystem). (I'm the technical lead for OpenJDK's Project Loom)
- dmitriid 6y agoAll languages end up with simple concurrency primitives such as async/await. No one takes the next steps and introduces the high-level primitives you actually need to work with actors and concurrency in a sane manner: monitors, messages, supervisor trees. Erlang has been around for thirty years, people.
- Someone 6y agoFTA: ”This is a common pattern: a class with a private queue and some properties that should only be accessed on the queue. We replace this manual queue management with an actor class […] Things to note about this example: - Declaring a class to be an actor is similar to giving a class a private queue and synchronizing all access to its private state through that queue. - Because this synchronization is now understood by the compiler, you cannot forget to use the queue to protect state: the compiler will ensure that you are running on the queue in the class's methods, and it will prevent you from accessing the state outside those methods. - Because the compiler is responsible for doing this, it can be smarter about optimizing away synchronization, like when a method starts by calling an async function on a different actor.” The article also links to - https://forums.swift.org/t/concurrency-actors-actor-isolation/41613 https://forums.swift.org/t/concurrency-actors-actor-isolatio... (pitch for implementing actors) - https://github.com/DougGregor/swift-evolution/blob/actors/proposals/nnnn-actors.md https://github.com/DougGregor/swift-evolution/blob/actors/pr..., which links to https://github.com/DougGregor/swift-evolution/blob/actors/proposals/nnnn-actors.md https://github.com/DougGregor/swift-evolution/blob/actors/pr... (proposal for implementing actors) I don’t know Erlang well, but what’s missing?
- dnautics 6y agoMonitors, linking, supervision trees, vm-level introspection into the state of the actors, distribution primitives that make actor identity nonlocal across clusters, actor cancellation (like kill -KILL), graceful actor shutdown, sane id serialization (how easy is it for me to serialize an actor Id, put it on a kafka queue and have it come back in a response so I can route the response back to the actor) etc, etc, etc.
- 6y ago
- Ciantic 6y agoDoes Swift's concurrency plan include thread-safety guarantees? I would like to see a higher level language than Rust that includes similar thread-safety guarantee "Thread safety isn't just documentation; it's law".
- onelovetwo 6y agocould you imagine having a function with many arguments and trying to find the async. internal func refreshPlayers(firstParameter: String, secondParameter: Int, thirdParameters: Float) async { } these small mistakes are starting to add up with Swift. They should really nip these things in the butt instead of adding upon the inconsistencies. Its better to make bold decisions now that you know is right than to change them 10 years from now when everyone is already use to them, which is what some older languages are dealing with now. Why not just have it be async func refreshPlayers() { }
- andrewbarba 6y agoThat's like saying: Could you imagine having a function with many arguments and trying to find the return type? Swift already uses the space at the end of the function declaration for things like throw and generic constraints. I personally don't see an issue with where it is other than I also write a lot of JavaScript and the context switching between languages might take a couple seconds.
- onelovetwo 6y agoThere's no reason for "throws" to be there neither, that's what I mean by these small mistakes adding up. If there was an argument that actually made sense, I'd understand, but there is none. this is the argument: (https://forums.swift.org/t/swift-concurrency-roadmap/41611/9 https://forums.swift.org/t/swift-concurrency-roadmap/41611/9)
- favorited 6y agoThe fact that you personally dislike something does not make it a mistake.
- onelovetwo 6y agoWell you can instantly tell its an async func, and it reads like english when reading it. Why do you think this would be a mistake? I'm just looking for a decent reasoning.
- mirekrusin 6y agoIf all private state is managed by serial queue, doesn't it mean you can easily deadlock yourself? Will swift statically reject deadlocks on reentrance?
- jarpadat 6y agoQueues are a dynamic concept; which queue you’re on is a runtime property. For this reason you may be familiar with assertions to check if a function is running on some queue. Actors however are a static concept, we know at compile time which actor is local, and if we have the right one active. So the check about whether you’re on right actor happens at compile time. You can think of it as if queues are part of the type system, and the compiler can work out statically what queue is used by any code, and so it can label an entire call tree’s queues by control flow analysis. Because of this you wouldn’t dispatch onto the same queue twice and deadlock. Rather, the compiler would see that the correct actor is local already in the call tree and there’s nothing to do so the dispatch is elided. It would only “switch queues” if it needs a new one. Besides the deadlock issue, the other advantage of this is it gets optimized out if you have several calls with the same actor.
- gaxxey 6y agoIf anyone wants to look through Chris Lattner original Swift Concurrency Manifesto. He goes into the bigger picture. https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782 https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9...
- layoutIfNeeded 6y agoYikes. Swift is becoming horribly complex and opaque. Yet another set of @shoehornedKeywords... Colored functions... Let me ramble a bit here. Apple’s platforms had always put users before developers, that’s why they were so successful. They’ve built the best user experience by far using nothing but a simple Smalltalk-esque language from the 80s. Look at their frameworks: CoreAudio, CoreAnimation, UIKit... Tremendously powerful, and the best in their league - in terms of possibilities, not necessarily developer experience! Others were shoving garbage-collected virtual machines in their devices, piling abstraction over abstraction, “fluent APIs” (remember those?); meanwhile Apple built an empire with this quirky dynamic language (plus some C++ under the hood), where you had to do manual reference counting as late as 2012! Developers were livid. “How am I supposed to program in this weird language?” “AutoLayout? Why can’t it be just like CSS or something??” But Apple didn’t give a shit, they knew their platform, their frameworks were the best, period. And whiny devs had to adapt to gain access to the richest cohort of users in the computing landscape. With Swift and SwiftUI we seem to be heading towards a different future, and I’m yet to be convinced that it’s the right one.
- IfOnlyYouKnew 6y ago> And whiny devs had to adapt to gain access to the richest cohort of users in the computing landscape > With Swift and SwiftUI we seem to be heading towards a different future, and I’m yet to be convinced that it’s the right one. The more things change, the more they stay the same...
- marketingPro 6y agoWhat makes you think "They’ve built the best user experience by far" I can't even say "best" experience let alone "by far". Apple is notoriously anti consumer on everything from hardware to software. I don't know what you are talking about. They had a fun signup screen when you first boot your iPhone. I have 0 ideas what you are talking about for macOS. I think their primary reason for success was their advertising/marketing.
- deleted 6y ago[deleted]
- tybit 6y agoI’m so excited to see them embracing actors. I don’t use Swift and likely won’t in the future but I really hope this decision bleeds over into other mainstream languages.
- PostThisTooFast 6y agoWhat are actors?
- todd3834 6y agoI find a lot of joy watching the JavaScript community and languages like Swift and Kotlin meet in the middle. Especially when you start looking at Typescript. Even Java has become tolerable these days.
- pornel 6y agoFrom a rustacean perspective I'm wondering why they aren't copying Rust's Send/Sync traits. They work so well: they're purely a type-system thing, without being tied to any coroutine runtime. And they allow creation of multi-threaded primitives, like actors, queues, locks, async, etc. in libraries without baking them into the language.
- pjmlp 6y agoYeah, which then ends up in incompatible runtimes, no thanks.
- son_ngu 6y agoWhat do you mean by incompatible runtimes, can you give an example?
- pjmlp 6y agoYes, https://blog.logrocket.com/a-practical-guide-to-async-in-rust/ https://blog.logrocket.com/a-practical-guide-to-async-in-rus... > Some libraries require you to use a specific runtime because they rely on runtime internals to provide a solid API for you to use or wrap their own API around an existing runtime. One example is the actix_web web framework, which wraps its own API around tokio. So if you want to use two libraries that follow this approach, now you have faced with the hurdle to combine two async/await runtimes into the application. Even C++ knows better in this regard.
- Matthias247 6y agoWe had this discussion before. Please don't repeat wrong statements over and over. C++ is in no ways different from Rust in this regard - they all just provide a coroutine transformation and future type, but not a builtin executor. And there is nothing more interoperable about C++ solution than about Rusts. Actually Rust ones might even be better, since there is at least a well-defined interface that executors should follow (through the `Waker` type). The C++ executor interface discussion is probably older than the coroutines proposal, but afaik there still hasn't happened any standardization.