I'm not a Go expert but preemptive scheduling of goroutines is supposed to be one of the primary benefits of Go as a language [1]. Although I'm not sure if there's any runtime that adopts preemptive scheduling as fervently as the BEAM. Even the regular expression engine is preemptive in Erlang, which prevents regular expression denial of service attacks [2].
I wasn't trying to imply that concurrency is the only way to mitigate ReDos attacks, I was just pointing out that the BEAM applies preemption seemingly everywhere, which I found interesting.
The BEAM misbehaves when a scheduler gets stuck in a process (or other activity) for too long. As a result, anything that can take a long time needs to have yield points. This is a human process; when I started using Erlang, garbage collection didn't yield, List1 ++ List2 took forever with large lists and didn't yield, we didn't have line numbers in backtraces, and we had to hotload our code uphill in the snow both ways. It's not unusual for a new OTP release to have added new yield points in BIFs or NIFs that could run long or even in core VM workings.
It wasn't non-cooperative pre-emptive scheduling for a while, which is why I kept pushing Elixir, until Go implemented it. Now Go has the same benefit as Elixir, but it's very, very fast.