Just to add to this, probably the biggest, most important reason to have language-level concurrency primitives is the 3rd party package ecosystem.
In Go, Erlang and Nodejs packages don't need to make any decisions about what kind of concurrency to use. The postgres driver in go will expose a blocking API, and the postgres driver in node will expose a continuation-passing-style API, or use promises. Everyone who uses those languages already knows how to consume those APIs. In these languages its very hard for an experienced developer to make concurrency mistakes - you can instead spend all your time thinking about the actual problem you're trying to solve.
In C, Swift and Rust this isn't true (yet). Despite all three of these languages having lots of tools for concurrency, the lack of standardisation means you can't just grab any postgres library. You also have to look at how that library does threading and figure out how to correctly integrate it with the threading model you're using in your own code. (Does it depend on tokio? Does it use posix threads? GCD? Green threads? Async?). This turns the 3rd party package ecosystem in these languages into a fragmented minefield.
As far as I know there are 4 main swift-based http server libraries[1] at the moment. Each one works slightly differently. Because they all roll their own concurrency tools, they're all mutually incompatible. Each one has implemented its own static file server, its own hsts header middleware, its own json parser, etc. I'm not sure about erlang but in node and go because the concurrency primitives are standard, they're all similar enough that you can migrate code between them or wrap a tool thats designed for one to work for another.
(Of course rust has tokio, but tokio is being rewritten[2]. Rust also has mio and raw threads. In a few years we'll probably have strong standards for both swift and rust, but until then the ecosystems will be a mess compared to the bliss of node and go.)
In Go, Erlang and Nodejs packages don't need to make any decisions about what kind of concurrency to use. The postgres driver in go will expose a blocking API, and the postgres driver in node will expose a continuation-passing-style API, or use promises. Everyone who uses those languages already knows how to consume those APIs. In these languages its very hard for an experienced developer to make concurrency mistakes - you can instead spend all your time thinking about the actual problem you're trying to solve.
In C, Swift and Rust this isn't true (yet). Despite all three of these languages having lots of tools for concurrency, the lack of standardisation means you can't just grab any postgres library. You also have to look at how that library does threading and figure out how to correctly integrate it with the threading model you're using in your own code. (Does it depend on tokio? Does it use posix threads? GCD? Green threads? Async?). This turns the 3rd party package ecosystem in these languages into a fragmented minefield.
As far as I know there are 4 main swift-based http server libraries[1] at the moment. Each one works slightly differently. Because they all roll their own concurrency tools, they're all mutually incompatible. Each one has implemented its own static file server, its own hsts header middleware, its own json parser, etc. I'm not sure about erlang but in node and go because the concurrency primitives are standard, they're all similar enough that you can migrate code between them or wrap a tool thats designed for one to work for another.
(Of course rust has tokio, but tokio is being rewritten[2]. Rust also has mio and raw threads. In a few years we'll probably have strong standards for both swift and rust, but until then the ecosystems will be a mess compared to the bliss of node and go.)
[1] https://medium.com/@rymcol/current-features-benefits-of-the-... [2] https://tokio.rs/blog/tokio-reform/