Go provides go routines as a building block, it's up to you to use that or use something else ( reactor pattern, epoll ect ... )
https://www.usenix.org/system/files/login/articles/chen12-06...
In epoll case, using RawConn.Read would look like this:
err := rawConn.Read(func(fd uintptr) bool {
nevents, err = syscall.EpollWait(int(fd), events[:], 0)
if nevents == 0 {
return false // try again
}
return true
})
Note that using RawConn.Read here is only necessary because epoll needs epoll_wait(2) instead of typical read(2). For ordinary file descriptors, like pipes, etc., setting them to non-blocking mode, wrapping them with os.NewFile, and using its ordinary Read/Write methods is sufficient.I don't know which category epoll fits into, but it seems like it should be treated optimistically, since epoll is used for non-blocking I/O.
https://utcc.utoronto.ca/~cks/space/blog/programming/GoSched...
Two points: a) native threads switching overhead is extremely low, probably lower than usermode Go thread overhead; b) the utility of usermode threads isn't to lower overhead.
It's a given that usermode threads introduce lots of overhead, if only because you're reimplementing the scheduling machinery that already exists in the kernel and that you're already paying the performance penalty for.
But theoretically, you could implement specially tailored scheduling algorithms specific for your app and gain an advantage there - kernel schedulers are general-purpose and can introduce latencies, starvation and other undesired special effects.
P.S. The Linux kernel also has interfaces for changing the thread scheduler algorithms. This gets you 99% of the way there - native threads + round-robin realtime thread scheduler gets you the performance benefits you want without having to rewrite schedulers and context switchers in usermode for every language runtime.
This is most likely very wrong.
In most apps, context switches would happen only due to kernel->user transitions anyway (such as an I/O syscall finishing or a timer triggering), so in most apps kernel context switches are indeed almost free.
To be significantly more efficient than the kernel there should be much more context switches than syscalls, which for non-synthetic programs is mostly only possible under heavy loads that can batch, with suitable I/O interfaces such as io_uring.
Kernel context switches are pretty light compared to the following slowdowns due to cache.
(Of course, in Go, the scheduler also weaves in the GC IIRC, so an apples-apples comparison may be difficult. Micro benchmarks are just not that useful.)
P.S.: this article seems to work under the assumption that 10,000 Goroutines is a reasonable upper limit, or at least it feels as though it implies that. However, you can definitely run apps with 100,000 or even 1,000,000.