7 ms·
This might be contentious, but I'm going to say it. If your program is multithreaded in a nontrivial way, Swift is not the language for you, at least yet. Firs
by raphlinus 8y ago
This might be contentious, but I'm going to say it. If your program is multithreaded in a nontrivial way, Swift is not the language for you, at least yet.
First, this article understates the danger significantly. It's not just that your token loader might be called twice, if you call this from two different threads you're fully in undefined behavior. Swift is "safe-ish" in that it's mostly safe in single-threaded contexts, but with data races that breaks down. It's even more confusing because some things are properly atomic (reference counts, manipulation of value types like arrays), but some aren't (access to fields in classes).
Second, the language is missing basic features that in modern programming would help tame this beast - the most important of which is a wrapper for internally mutable state that is protected by a mutex. Grand Central Dispatch can help with some of this.
Third, there are unexpected performance losses. For example, the closure for running a block on a sync "queue" is heap allocated (because the type of such a closure is the same for sync and async, and of course the latter has to be heap allocated). This will probably be fixed, but I think is symptomatic.
Fourth, there's no official concurrency model. Again, it's confusing because there are some things that are obviously intended to run with concurrency. In fairness, Rust doesn't have a formal concurrency model yet either, but it is straightforward to apply reasoning from C++, and also reasonable to expect guarantees if the primitives such as Mutex, channels, etc., are used properly.
Fifth (and related), there are no atomics in the language or the standard library. Apparently OSAtomic is deprecated, and they now recommend the use of C atomics, but you're in the territory about having to reason about the polyglot combination of Swift and C.
Likely all this will improve, but in the meantime it's important to realize how immature Swift's concurrency is.
- saagarjha 8y ago> It's not just that your token loader might be called twice, if you call this from two different threads you're fully in undefined behavior. This is addressed in the article, and a resolution is provided. > It's even more confusing because some things are properly atomic (reference counts, manipulation of value types like arrays) Modifying an array is not atomic, AFAIK. You cannot safely do this concurrently.
- raphlinus 8y ago> This is addressed in the article, and a resolution is provided. "Undefined state" is the language used in the article, and has no precise technical definition. The article suggests to me simply that a method might be called twice, and makes no mention of your bank account being emptied or demons flying out of your nose, which is the consequence of undefined behavior you should expect. > Modifying an array is not atomic, AFAIK. You cannot safely do this concurrently. I'm happy to correct errors (I'm not a Swift expert), but I'm going on the basis of copy-on-write semantics and the fact that `isUniquelyReferenced` seems to be implemented atomically - this is stated fairly clearly on the official Apple blog at https://developer.apple.com/swift/blog/?id=10 https://developer.apple.com/swift/blog/?id=10 , but that might be out of date.
- saagarjha 8y agoTry compiling this with swiftc -sanitize=thread: import Foundation let q1 = DispatchQueue(label: "1") let q2 = DispatchQueue(label: "2") var a = [0] q1.async { a[0] = 1 } q2.async { a[0] = 2 } You'll get an error at runtime that there's a data race.
- raphlinus 8y agoThis is a subtle point. It's not the array assignment that's the race here, but the update to the shared (by closure capture) `a` variable. You'd get the same data race if `a` were an `Int`, and we don't say that integers are non-thread-safe. If each closure had its own reference to the array, it would be copied on write. I believe this is pretty good evidence for my claim that understanding the thread safety is confusing.
- akvadrako 8y agoUm, this is all very standard stuff - if you want thread safety you should use queues or locks. Just because a language doesn't have first-class support for multithreading doesn't mean you shouldn't use it. You should use explicit synchronisation even when using Python, which has that terrible global lock. Relying on atomic operations is too error prone and not explicit enough.
- raphlinus 8y agoC and C++ are sharp tools. They have extensive undefined behavior, but also a formal memory model, so it means something to say, "this code is correct." They also have a culture of people who understand how to deal with undefined behavior, and tools (sanitizers) that make it more feasible. Rust is safe, unless you go out of your way to be unsafe. It's not just "use a lock," it's "if you try to access shared mutable data without a lock, your program won't compile." Java has data races but not undefined behavior. It also has a formal memory model. Swift gives you none of that. Treating threads as a library rather than something with language support is something that might have seemed like a good idea 20 years ago, but now I think we know better.
- tinus_hn 8y agoThe C model is ‘there is only one core, race conditions don’t exist. Multithreading is undefined.’ How is that better?
- lifthrasiir 8y agoThat has been changed in C11 to match with C++11, in case you are not aware.
- Matthias247 8y agoI agree that there are 1000 ways to create concurrency issues in Swift, as shown in the article. However I fail to see how it's worse than most other multithreaded languages. C++, JVM languages, Go, Python - they all don't provide a lot of support for writing correct thread-safe programs. Swift/Objective-C might even have an edge compared to some of them, due to providing dispatch queues in the standard library - which are fairly easy to reason about, as shown in the article. I see those as better than the average user creating lots of threads on their own, and then try to synchronize things via mutexes. There might be a performance overhead, but I would only look at that after the program is correct. There might be languages that provide more help here, e.g. Erlang, Haskell or Pony. But those are not mainstream, and might also not good choices for things where Swift is an alternative (client-side applications). Ada might have been better too, with builtin Task and communication constructs, but that falls into a similar category. Then we have Rust, which might be the thing you are referring to as a better alternative. It is in the sense that it can prevent most threading issues at compile time - I will refrain from saying "all" since people will write unsafe code, and there can still be deadlocks and logical race conditions. However even Rust thought Rust has lots of tools for preventing concurrency issues, it still doesn't have builtin ways for easy concurrent programs - and e.g. for solving the problem that is described in the article. If one would implement the given solution in Rust, there might be lots of fighting with the borrow-checker involved, and potentially the same amount of heap allocations (callbacks isn't a paradigm that works well in Rust). Obviously there are more idiomatic solutions.
- WoodenChair 8y ago> However I fail to see how it's worse than most other multithreaded languages. C++, JVM languages, Go, Python - they all don't provide a lot of support for writing correct thread-safe programs. Swift/Objective-C might even have an edge compared to some of them, due to providing dispatch queues in the standard library - which are fairly easy to reason about, as shown in the article. A couple corrections: - Dispatch Queues are not a part of the Swift standard library. They are part of Grand Central Dispatch which is almost always present where Swift is present but is actually a separate project. - JVM languages, well specifically Java at least, do have synchronization primitives built-in. It's part of the Java language spec. You can mark any method as synchronized for instance and it will automatically be mostly thread-safe.
- ken 8y agoI agree. I have a heavily multithreaded program, and I had to write my own thread-safe data structures, and spent ages debugging and tuning them. Then one weekend I read about Clojure core.async, and wrote a little 50-line prototype (much simpler and easier), and got something that worked correctly every time (first try!), and was (without tuning) only about 15% slower than my production system. I've read about how they plan to glue some better concurrency support onto Swift, and I dread it. The language is already so complex that they're having trouble with the basics. I've got workarounds in my app because switching on an enum doesn't always work right (SR-1121). Now we want to add concurrency after the fact? Oh boy. If I had to do it again, I'd definitely write the core of my software in some other language (maybe Clojure), and just use Swift for the GUI. Swift looks like a language designed for AppKit/UIKit, so it's fine if that's all you need it to do.