[0] https://github.com/crystal-lang/crystal/wiki/Threads-support
[1] https://github.com/crystal-lang/crystal/tree/thread-support
[0] https://github.com/crystal-lang/crystal/wiki/Threads-support
[1] https://github.com/crystal-lang/crystal/tree/thread-support
It'll be going away -- rewritten in Rust. Crystal has been great, and I really like the way it started going, but...
Development of the core language seems to have died off -- either through lack of updates and communication like you've mentioned or development. Regardless, the core team doesn't inspire me enough to take a chance on the language any more -- I've lost hope for a 1.0 release any time in the near future. I'll be killing off my BountySource[0] recurring donation as well.
There was/is so much potential in Crystal -- it really is great (joy to write, performant) -- but without real sponsorship/leadership, it just seems to be floundering. I hope I'm wrong and these are just growing pains!
Thanks for the honest insight -- really, that's so much better than the general silence we've been seeing.
A regular "State of Crystal-lang" blog post would be sufficient for me to hang on. Even if the posts might be "haven't done much. this is what we'd like to work on next, these are the roadblocks, etc. you can help by doing XYZ".
Looks like we can probably do at least one blog post a month, we'll discuss it internally.
For me personally, that's what's so good about multi-threading. It's the best tool for my job.
And if speed is important to you, then you want to use all 4/8/16 cores of your machine, not just one.
The advantage of multithreading in a web application is not "using all cores" but rather that the application can spawn a thread to "asynchronously" wait for a response from the network or database while it continues doing other work in the main execution thread, instead of "synchronously" blocking all work while it waits.
In other words, web applications leverage concurrency with their use of multithreading, not parallelism.
I will caveat that having more cores can give concurrent applications a small performance improvement, because even though each thread is only using a CPU core at around 0 to 15% (in short bursts) of capacity, switching between two threads on the same core is a context switch, a fairly expensive operation. More cores means fewer context switches.
But, unlike with parallelism, making use of more cores is not the reason concurrency offers performance benefits. ~99% of the performance benefit of concurrency is realized on even a single-core machine, whereas parallelism by definition requires multiple cores to offer a speedup. Concurrent applications such as web servers (e.g. Apache, Nginx) typically spawn hundreds or even thousands of threads per CPU core.
While an application may be fine without good/native support for parallelism in most cases, it's really nice to be using a language that allows you to bolt it on easily when it turns out that you _do_ need it. At this point, it's a language feature that's not worth leaving on the table without compelling rationale.
Arguably, the class of high-level dynamic languages that were on the rise in the mid-00s (Python, Ruby) lost a lot of steam to newcomers like Swift and Go due to the language designers' hesitation around embracing parallelism. Crystal should not repeat that mistake.
We're not there yet but I'm confident the main barrier is time and manpower to implement it, not willpower or technical reasons.
>the class of high-level dynamic languages that were on the rise in the mid-00s (Python, Ruby) lost a lot of steam to newcomers like Swift and Go due to the language designers' hesitation around embracing parallelism.
Quite the opposite, Python has a dominant and rapidly-growing market share (as the developer-facing API wrapping Fortran/C/C++ libraries) in CPU-bound, massively-parallel applications (NumPy, Tensorflow, etc).
Swift and Go are odd examples to make your point: Swift's support even for concurrency[1] is a low-level and unergonomic Objective-C library, and for parallelism (or implementing other concurrency primitives) an even lower-level and less ergonomic paper-thin Objective-C wrapper around pthreads.[2] That's still better than Swift on Linux, where the only option for either is plain pthreads.
Go has excellent concurrency support (with goroutines, aka fibers, aka green threads, which execute one-at-a-time on a single CPU core and from within a single actual OS thread), but for multi-core parallelism its standard library also offers nothing more than a paper-thin wrapper around pthreads.[3]
Even C++11 offers a substantially higher-level and more ergonomic parallel-threading API than either Swift or Go.[4] Among recent, relatively mainstream, general-purpose languages, the examples that come to my mind as exemplifying strong parallelism support are Rust, Julia and recent versions of Java (Streams) and C# (TPL, PLINQ).
[1] https://developer.apple.com/documentation/dispatch
[2] https://developer.apple.com/documentation/foundation/thread
Like I said, your argument is totally valid, and I don't contest it. It's not that most applications really need native threads, or that people are choosing the best alternatives for threaded applications. It's just that people don't want to use a language that will obstruct access to OS-native threading should they decide they need it, partially because it's hard to accurately anticipate all of an application's needs ahead of time.
I'm assuming here that this is not just about multiple threads of control (which can also be achieved through having multiple processes), but having threads interact through shared memory rather than through message passing.
The problem is that with shared memory, there's a constant tension between performance and safety. Pretty much all safe approaches introduce either contention (through mutexes or other synchronization constructs) or considerable overhead (such as the O(log n) factor typical for purely functional data structures or creating duplicates to avoid contention). In order to write actual high performance code accessing shared memory, you will typically have to deal with inherently unsafe constructs.
In most cases, it's far easier to optimize message passing, perhaps throwing in some specialized shared memory constructs (such as Mnesia for Erlang) in order to make some common use cases less cumbersome. Note that with current memory hierarchies, the overhead of message passing compared to shared memory may be less than you think; any data that's actually used by more than one core has to pass through the L3 cache at a minimum.
I think that the reason why more people don't use message passing approaches is (1) that few languages actually support it well and (2) that message passing as a programming paradigm can be far more intimidating than shared memory, because causality is more difficult to reason about (though the ease of use of shared memory models can be deceptive and it's easy to run into difficult to diagnose correctness or performance problems). But technologically, message-passing is much easier to support. I've written highly parallel code in Python, Ruby, and OCaml, all of which have a GIL and cannot support shared memory parallelism. But they all have (near) universal serialization, so implementing basic message passing is trivial.
As an example, one of the main attractions of Go is, I think, that it provides a very accessible programming model for message passing (building on top of CSP). And we have PGAS approaches, which essentially simulate shared memory on top of a distributed architecture.
This is not to say that message passing is a panacea; there are plenty of use cases where it's a poor choice. For example, I'm currently working on a problem that is essentially about performing a reduction operation in parallel on multiple threads, where the operation is typically defined through a multi-GB data structure. Copying that data structure to every single thread is prohibitive, and keeping it in one thread would kill all the possible parallelism. But message passing is plenty good enough for 90% or so of all use cases.
Generally, I would phrase it so that the problems that shared memory and message passing have are duals of each other: shared memory is concerned with contention as a source of cost and complexity, message passing is concerned with communication as a source of cost and complexity. For the majority of problems, you can engineer solutions that fit either model (though they may look substantively different), so not using shared memory is not going to be a problem for those.
Asynchronicity is a different concern and does not actually require threads.