← Back to context

Comment by Const-me

3 years ago

As a developer, I often choose higher-level APIs not listed in that article.

On Windows, OSX and iOS the OS userland already implements general, and relatively easy to use, thread pools. On Windows, see CreateThreadpoolWork, WaitForThreadpoolWorkCallbacks, etc. It’s easier to use threads with locks while someone else is managing these threads. On Apple, the pool is called “grand central dispatch” and does pretty much the same thing.

Modern Windows kernel supports interesting synchronization APIs like WaitOnAddress, WakeByAddressSingle which allow to implement locks without the complexity or performance overhead of maintaining special synchronization objects.

Linux kernel implements performant and scalable message queues, see mq_overview(7). And it has synchronization objects like eventfd() and pidfd_open() which allow to integrate locks or other things into poll/epoll based event loops.

GCD/libdispatch is a fantastic approach to concurrency and you can build and install support for non-Apple operating systems:

https://github.com/apple/swift-corelibs-libdispatch

Here’s a simple echo server:

https://github.com/williamcotton/c_playground/blob/master/sr...

Here’s a simple multithreaded database pool:

https://github.com/williamcotton/express-c/blob/master/src/d...

  • libdispatch idea of using specific queues for serialized concurrency is nice, but its abstractions on top are unfortunately not as efficiently designed as it could be; `dispatch_source` doesn't allow for direct completion based IO schemes (io_uring, IOCP), pushing to a `dispatch_queue` always requires a heap allocation, `dispatch_semaphore/dispatch_sync` blocks the thread instead of yielding asychronously (can cause "thread explosion"). Systems like Go don't have these constraints I don't think.

    • > `dispatch_semaphore/dispatch_sync` blocks the thread instead of yielding asychronously (can cause "thread explosion")

      dispatch_group can do waits without blocking threads, but asynchronicity is overrated, and dispatch's design makes it easy to overdo. Having default global concurrent queues was probably a mistake.

      Swift concurrency is a more modern design here.

If you’re in C++ land, you might take a look at Boost ASIO. It’s not just for IPC and would give you portable code.

Please don't use posix message queues (`mq_*`, `mq_overview`, etc) when you're writing a program with a single address space (like something that uses threads in a single process).

Posix message queues (the `mq_*` functions) are much slower than optimized shared memory queues using typical atomics and have semantics that are unexpected to most (they persist like files after process termination, because of what they are designed to be used for, they have system level limits on size and size of items, etc).

A simple benchmark vs rust's `std::sync::mpsc` queue shows `std::sync::mpsc` is 28.6 times faster when using 1 producer and 1 consumer, and is 37.32 times faster with 2 producers and 1 consumer.

what about "green threads" that is not managed by the OS like https://tokio.rs ?

  • Tokio is using the Rust async features, which are not green threads. In the former code has to explicitly mark potential yield points, in the latter green threads can be scheduled by the runtime without any help from the code itself.

    As a historical note, Rust used to have green threads but they were abandoned a long time ago. This is a good talk about both the differences between different forms of concurrency/async and Rusts history with them: https://www.infoq.com/presentations/rust-2019/ (includes a transcript)

  • I believe these green threads work well when there’s good support in both language, runtime and standard library. I have built complicated concurrent software in C# with async-await. I don’t program golang but I heard the concurrency model works rather well in golang too, probably for the same reason as C#: good support in the language and the runtime.

    I have no idea about Tokyo. I don’t program Rust, and the feedback I read about async/await was mixed.

  • You only ever need this if you're trying to have hundreds of thousands to millions of threads. It's a very niche problem to have

    • It's a niche problem to have because our current programming paradigm treats concurrency as a second-class citizen. The invariants of most languages do not include those required for mass concurrency.

      Functional programming better aligns with the requirements, which is how you arrive at Erlang and Elixir. Every map() function can be trivially replaced with the concurrent cmap(), because the side effects that would make it non-trivial are impossible to express.

      Given that Moore's law as it applied to single threaded performance is dead and in the ground, it makes a lot of sense to start paying more attention to systems that treat concurrency as more than an afterthought.

      1 reply →

  • Green threads do not make use of multiple cores of a modern processor.

    • It's entirely possible for a green threads implementation to schedule the green threads over multiple OS threads, thus making use of multiple cores. The programming language "Go" being a popular implementation of this.