7 ms·
True, but if you regularly have 1000 runnable goroutines then could you not reconfigure your app to have, say, 64 runnable goroutines, and get better throughput
by gnfargbl 6y ago
True, but if you regularly have 1000 runnable goroutines then could you not reconfigure your app to have, say, 64 runnable goroutines, and get better throughput? Large numbers of goroutines do seem to be a good fit for problems which are mostly waiting on the network.
- jeffbee 6y agoThe architecture of Go forces you to have 1 goroutine servicing every socket, so the number of runnable goroutines will then be at the mercy of your packet inter-arrival process.
- orisho 6y agoDoes it? You could implement a classic select-based reader and writer in a single goroutine, to handle many sockets at once.
- jeffbee 6y agoPlease describe how you would actually do that in Go.
- lossolo 6y agoUsing epoll directly (via syscalls) like here https://github.com/tidwall/evio https://github.com/tidwall/evio
- refenestrator 6y agoMy information is outdated, but last I knew, Go always parked a goroutine when making a syscall. Seems like a lot of overhead to read bytes that you know are there from an epolled fd.
- coder543 6y agoNot exactly. It depends on the syscall. Syscalls that are known to be slow are treated the way you're describing, but syscalls that are often fast are treated optimistically in a way that allows the work queue to be stolen and spawned onto a new thread only if the syscall doesn't return quickly. 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/GoSchedulerAndSyscalls https://utcc.utoronto.ca/~cks/space/blog/programming/GoSched...
- refenestrator 6y agoread(2) has to fit into the "might block" bucket, though, right? It would take a very smart runtime to figure out you just called epoll recently and established it won't block.
- WGH_ 6y agoYou could put epoll fd in non-blocking mode, wrap it with os.NewFile, use SyscallConn() to get syscall.RawConn object, and then use its Read method. Its Read method is special: you can return "not ready", and it will use the Go runtime poller to wait until it's readable, in this particular case effectively putting epoll in a epoll. 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.
- im3w1l 6y agoI'm not too familiar with go, but could you make the socket-routines very lightweight? Like dump the work on a threadpool, and then go back to sleep?
- closeparen 6y agoAren’t sockets the textbook example of what’s usually blocked? When do you have 1000 sockets all ready to read?
- jeffbee 6y agoSystems often solicit these kinds of packet storms, for example in root-and-leaf search architectures. See "TCP incast" for writings about this problem. https://www.usenix.org/system/files/login/articles/chen12-06.pdf https://www.usenix.org/system/files/login/articles/chen12-06...
- Thaxll 6y agoGo does not forces you to do any of that: https://github.com/panjf2000/gnet https://github.com/panjf2000/gnet ( and as you can see it's as fast as the fastest C++ lib ) Go provides go routines as a building block, it's up to you to use that or use something else ( reactor pattern, epoll ect ... )