10 ms·
Concurrency in Swift: One possible approach
- zzzcpan 9y agoMaybe it's time for Swift to decide whether it's going to stick to multicore systems or actually leave this single box mindset and enable distributed systems programming. I think designing for distributed systems first could lead to a lot of right choices from the beginning and would still allow for various optimizations later to get the best performance from multicore. Alternative is not very promising and provides a lot of room for really stupid mistakes. Either way, I'm glad to see actor model getting more traction.
- throwaway91111 9y agoWell, multicore isn't going away; concurrency (if not parallelism) is necessary for responsive UIs. Did you have any specific improvements? The big requirements for distributed programming are mostly protobuf serialization and tcp/http; swift has both already.
- zzzcpan 9y agoI was thinking more in terms of first class support for distributed actors, with global addressing, message routing, etc. Not designing for multicore performance first, the way Pony did.
- spearo77 9y agoAnd the supporting swift Pull Request is https://github.com/apple/swift/pull/11501 https://github.com/apple/swift/pull/11501
- perfectstorm 9y agocorrect me if I'm wrong but this is what C# has? I recall writing async and await when I did a Windows Phone app many years ago.
- aaronbrethorst 9y agoYes, exactly. FTA: There is a well-known solution to this problem, called async/await. It is a popular programming style that was first introduced in C#
- panic 9y agoDefinitely some cool ideas here! The motivation seems a little high-level to me, though. Language features ought to help you solve concrete, real-world problems. I can understand what problem async/await solves -- there's a great example with code -- but the actor stuff isn't as clear-cut.
- 0xbear 9y agoAny such model MUST show what error handling will look like. Otherwise you end up with "log to console and ignore" approach so prevalent in Android SDK examples, which people _will_ copy unchanged.
- RubenSandwich 9y agoConsidering that Mr. Lattner pays tribute to the Akka library which is based on Erlang style actors. I'm going to assume he is going to go with Erlang style error handling. Allow actors to throw and allow supervising actors to catch those throws.
- haimez 9y ago"Erlang style" supervision error handling is short-hand for opt-in, optional error handling without any compiler support. That's a pretty serious trade-off to not address directly.
- colefichter 9y agoWhat you describe sounds like a consequence of Erlang's dynamic type system, rather than its concurrency model. Besides, don't most languages have "opt-in" error handling? For example, I work mostly in C# and if you don't catch an exception, your program will crash. How is that different? At least in Erlang, my program might restart itself. Sadly that doesn't happen on most other platforms.
- andrewbarba 9y agoHe discusses error handling options in the full proposal here: https://gist.github.com/lattner/429b9070918248274f25b714dcfc7619#make-async-be-a-subtype-of-throws-instead-of-orthogonal-to-it https://gist.github.com/lattner/429b9070918248274f25b714dcfc...
- jzelinskie 9y agoAs someone who doesn't develop for the Apple ecosystem, I can't quite find one place that articulates well what the Swift development philosophy is and what it brings to the table besides just being modern language with shims for interacting with legacy Apple APIs. Why would I use Swift on Linux? Most of the posts that I see related to Swift are RFCs evaluating solutions to problems in other languages. I rarely get to see the actual solutions being integrated into the language. Is this just a result of HN readers caring more about language design than learning to leverage the Swift language?
- ben_jones 9y agoAs someone who is starting to dabble in the Apple ecosystem via Swift - I heartedly agree. There are plenty of resources for new developers who are trying to learn programming via Swift, but if you are a seasoned developer who wants to jump straight into best practices and architectures while learning the syntax and features at the same time... you're going to have a bad time. I also feel like with Swift people are so focused on pumping out their mobile app that best practices go out the window, or even that the knowledge is seen as such a valuable and proprietary skillset that the senior and experienced developers simply keep it to themselves and don't publish (compared to other OSS ecosystems)...
- adamnemecek 9y agoThe objc.io books on Swift are awesome https://www.objc.io/books/ https://www.objc.io/books/
- valuearb 9y agoWell legacy Apple APIs happen to be the Apis for the highest revenue software market in the world.
- tommymachine 9y agoI just wrote a swift api that I'm running on Ubuntu 16.04. I wasn't hesitant to use swift because first, it substantially outperforms node on many benchmarks [1] [2] and second, the community of people who are excited about swift seem to have made up for its young age. You can find workable tutorials and help online for server side Swift. And there are multiple backend frameworks to choose from. Having type safety on the server is as great as it is on the client. It's also great to be able to write the server in the same language as the client. Makes for a VERY smooth dev process. Why wouldn't you use swift on the server? (my guess is fear of new tech -- which can be healthy many times but, IMHO, at least in the case of server side swift, it may be a bit irrational!) 1. https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=swift&lang2=node https://benchmarksgame.alioth.debian.org/u64q/compare.php?la... 2. https://medium.com/@rymcol/linux-ubuntu-benchmarks-for-server-side-swift-vs-node-js-db52b9f8270b https://medium.com/@rymcol/linux-ubuntu-benchmarks-for-serve...
- lobster_johnson 9y agoI'm surprised that async/await is the preferred idiom. Having worked with async/await in Node.js a lot, it is of course a significantly better solution than plain promises, but it is also quite invasive; in my experience, most async code is invoked with "await". It's rare to actually need to handle it as a promise; the two main use cases where you want to handle the promise as a promise is either when doing something like a parallel map, or when you need to deal with old callback-style code where an explicit promise needs to be created because the resolve/reject functions must be invoked as a result of an event or callback. Would it not be better to invert this -- which is the route Erlang and Go went -- and make it explicit when you're spawning something async where you don't want to deal with the result right away? In Go, you just use the "go" keyword to make something async. So the caller decides what's async, not the callee. If callers arbitrarily decide whether to be async or not, a single async call ends up infecting the whole call chain (which need to be marked "async" unless you're explicitly handling the promise/continuation without "await").
- melling 9y agoIt’s the easiest to implement and could possibly make it into Swift 5. Sounds like he wants Actors just as much.
- wahern 9y agoBoth Erlang's and Go's concurrency model is thread-based. An "async" operation invokes a function on a separate call stack. The function doesn't need to know which call stack it's invoked on. The functions that that function calls don't need to know, either. Lua's coroutines are similar. The problem with the above approach, however, is that you can't create hundreds of thousands of threads while also transparently supporting the traditional C ABI. C ABIs aren't designed to dynamically grow the stack, and so any thread that needs to invoke C (or Objective-C) code must always create threads with very large stacks (on the order of hundreds of several hundred KB or even megabytes) if they want to support legacy code.[1] Languages that don't want to put the effort into growable stacks have no choice but to implement a solution that requires annotating the function definition, directly or indirectly.[2] The annotation tells the compiler to generate code that stores invocation state (temporaries, etc) on a dynamically allocated call frame (usually allocated by the caller) rather than pushing them onto the shared thread stack. This is true whether or not the function is a coroutine that can yield multiple values before finishing. Basically, without using a thread-based model, you can never put the caller in full control. [1] Work on GCC Go necessitated adding a feature to GCC called split stacks. So GCC can actually compile C and (I think) C++ code that can dynamically extend their stacks. However, for it to work properly you have to compile everything with split stacks, including libc and all dependent libraries. [2] Technically a compiler could emit two versions of a function, one that uses the thread stack for temporaries, and one that uses a dynamically allocated frame. That would put the caller in control. Some languages with very complex meta-programming capabilities (like various Lisps) can do this by making the await keyword a function which literally re-writes the callee into an async function that stores temporaries on a caller-provided buffer. So you can implement it without any compiler support. But it's still limited because the functions invoked by the async function would have to be rewritten recursively. The thread-based design is really the best approach, but it's a non-starter for many languages because of concerns about interoperability and legacy support. JavaScript rejected a thread-based model because existing implementations were too heavily dependent on the semantics of the traditional C stack, and they didn't want to throw away their existing investments.
- haimez 9y agoFeels like the elephant in the room for the description is not once mentioning Futures but instead jumping straight to using async / await keywords and the actor model. As evidenced by C#, you can't avoid leaking the type signature of async operations if you actually support generic programming- so while that's a nice ergonomics improvement, it only adds complexity to the actual concurrency model. Go enthusiasts out there will appreciate that go solves this by refusing to support PROGRAMMABLE generic abstractions at all (looking at you, channels and map). Referencing the actor model and making it first class is interesting, but probably a mistake. Actors are hard to reason about because they're so flexible. Pony is a good recent attempt at combining static types with actors, bit they didn't put performance into the "non-goals" section of their language spec. If you want task level concurrency and you want it to play nice with your type system, you have to start with Scala and work backward to the alternative implementation choices you're going to make because it checks all the boxes of all the "goals" and ALSO has a very mature actor model implementation that doesn't require promoting actors to keyword status in the language.
- andrewbarba 9y agoHe heavily discusses Futures, and a sudo-implementation, in the full proposal here: https://gist.github.com/lattner/429b9070918248274f25b714dcfc7619#entering-and-leaving-async-code https://gist.github.com/lattner/429b9070918248274f25b714dcfc...
- haimez 9y agoForgive my naivety, but parent article seems to contradict the github post linked in your reply in quite a few ways. What is the time / design iteration relationship between the two, and why (not to put you on the spot)? These are VERY different proposals.
- andrewbarba 9y agoHe links this "full" proposal, in the proposal linked by OP. That's how I found it
- 9y ago
- tambourine_man 9y agoMust be crazy for Apple to have such an influencing voice on Swift and LLVM working for its archenemy
- melling 9y agoWhat makes you think they are archenemies? Android became the Windows to iOS. Someone was going to fill in that gap that Apple tends to leave open. Probably better that it’s Google than Microsoft. The world got another major OS.
- tambourine_man 9y agoNah, I have fond memories of when it was all against MS Goliath. Common enemy, etc. Apple and Google made a great partnership. It's now 2017 and iCloud/web services still kind of suck while Apple continues to make the best OS and hardware (IMO, of course). We used to be able to have both.
- valuearb 9y agoGoogle's paying Apple billions per year and has one of it's senior software architects helping improve their core development language. I'd say crazy like a fox!
- MBCook 9y agoNot only that, Apple is apparently responsible for 50% of the mobile ad revenue Google pulls in.
- jorblumesea 9y agoDoesn't Chris work at Google now? Where does he find the time to just go off and implement the Actor Model? I assume this was done when he was still at Apple?
- valuearb 9y agoSwift is open source. He's making a proposal. He also seems to carry a little weight in the community. I'm guessing his proposal might get implemented.
- jorblumesea 9y agoYeah but if you make a proposal with a pull request it has to work, at least in theory. I don't think I've ever just "implemented the actor model" in a weekend haha. Props. I guess when you wrote the language...
- samcat116 9y agoI mean he wrote the beginning of the language at home for like a year without anyone at Apple knowing.
- melling 9y agoHe’s working on the Google Brain team. Maybe he’s going to use Swift and needs the concurrency features? :-)
- hellofunk 9y ago> the speed of light and wire delay become an inherently limiting factor for very large shared memory systems Wow, cool to see actual speed of light mentioned as a factor, never seen that before.
- tambourine_man 9y agoGrace Hopper - Nanoseconds https://youtube.com/watch?v=JEpsKnWZrJ8 https://youtube.com/watch?v=JEpsKnWZrJ8
- lngnmn 9y agoSoftware interrupts is the right answer to concurrency nonsensical hype. Sometimes old is gold. Wrapping specialized interrupt handlers into some higher lebel API, such as AIO, is the right way. Async/await is a mess. Concurrency cannot be generalized to cover all the possible cases. It must have specialization. Engineers of old times who created the early classic OSes were bright people, contrary to current hipsters. Once popularized, flawed abstractions and apis such as pthreads would stick (only idiots would accept shared stack and signals). Look at how an OS implements concurrency and wrap it into higher level API. That's it.
- dullgiulio 9y agoYour comment is rude and factually very wrong. The new languages we discuss are definitely not written by "current hipsters", whatever you meant with that. How does the OS implement concurrency, which is a userspace problem? Software interrupts alone are not enough, you still need some language support to it. I am not sure you really know what concurrency even means...
- lngnmn 9y ago> concurrency, which is a userspace problem This is another gross misconception, like an attention to manage resources from the userspace, the way JVMs or Node are trying to do by poorly reimplementing OS subsystems. Concurrency primitives must be implemented by an OS - it will eliminate all the userspace problems - no busy-waiting, no polling, no waking up for every descriptor, etc. Ignoring an underlying OS instead of having thin wrappers, like Plan9 from userspace does, is exactly what is called ignorance (lack of awareness of a better way).
- liuliu 9y agoAsync / await also need a idiom to accompany on how to support cancellation. In practice, cancellation happens a lot because the unbounded latency for async operations. Without a throughout support, async / await syntax is probably usable on server side somewhat but still hardly applicable on client side (as the latency is unbounded). On the other hand, C# does go through the pain and added cancellation support to all its standard libraries async API.
- jakobegger 9y agoI can't upvote this enough. In practice, supporting cancellation is one of the most important and tedious parts of asynchronous programming. It's easy to start a some task with a completion handler. Even error handling isn't that hard, since communication of a failure goes in the same direction as communicating success. But cancellation goes into the other direction! So whenever you start an async operation, you need to somehow store a handle or something, so you can cancel it later on. The actor model makes this even worse -- how do you cancel a command that you previously sent? How do you tell an actor that they don't need to perform an operation, if the operation is still on the queue, or that they should abort the operation if they are already working on it? If the actor model doesn't have an answer to this problem, developers won't be able to use it as is, and they will have to build additional abstractions on top of it before they can use it.
- zzzcpan 9y agoYou create an actor that performs an operation and simply kill it if you want to cancel that operation. Promises/futures with cancellable contexts are equivalent to actors.
- Matthias247 9y agoYes and no. You can for sure implement actors which support cancellation or killing them, the only question is how. In lots of actor frameworks you send the actor a close message that triggers it to shut down - and you can also send it a cancel message after some request. However if the server side actor is not implemented fully asynchronously and doesn't read it's messages, it might take a long time for the cancellation to happen -> exactly the same time which it takes for the operation to finish. E.g. if the remote actor is implemented in some sequence like: receiveRequest(); doSomethingWithRequestWhichIsProbablyBlockingRequestsToOtherActors(); sendResponse(); then there is no cancellation possibility. If the other actor is implemented fully asynchronously then it could pick up the cancellation and do something with it. But it's harder to implement, becaue then you have again state machines everywhere. In erlang killing actors without messages might work, because it's supported deeply within the runtime.
- weitzj 9y agoRelated Tweet from Joe Armstrong: https://twitter.com/joeerl/status/898444575668404224 https://twitter.com/joeerl/status/898444575668404224