How Haskell threads block
If you’ve played around with Haskell for any amount of time, you probably know it has a M:N threading model with many Haskell threads multiplexed by a userspace scheduler onto a few operating system (OS) threads. Since a Haskell thread uses much less memory than an OS thread, it becomes possible to spin up large numbers (hundreds of thousands to millions) of Haskell threads. This threading model has great benefits for readibility, since most programmers find it easier to follow a linear “top-to-bottom” path through the code than to follow along nested callbacks. There are also some drawbacks to this approach. For example, managing blocking system calls becomes much harder, since a thread that is doing a blocking operation cannot serve normal workloads. There is also much more bookkeeping to be done in the runtime, which would normally be left to the operating system.
Source: How Haskell threads block, an article by Wander Hillen.