Handle no more connections or out of shared memory
The number of threads depends on a previous result.


Could you rewrite this to have fewer queries returning more rows each?  If you launch one thread per row to be returned, you are probably introducing more overhead than you are removing.


  If I hardcode the maximum number of connections I will run out of connections when something else is connected. 


Unless that "something else" is another golang program doing much the same as the first one, or something equivalent, then you can make max_connections only be slightly larger than the max pool size in golang, and you shouldn't have a problem.  It does require you to have a fairly good idea of what the other things are.


  On the other hand if I hardcode a too low number of allowed connections from the golang program performance will be unnecessary limited.


Have you verified this?  Having 300 connections fighting over 16 CPUs and 4 hard drives is not going to enhance productivity.  There is only so much hardware, and now you have to unproductively fight over spinlocks (for example) as well as do the real work.


  I am thinking of running PgBouncer between the database and the golang program, hopefully accepting all the connections from the golang program, allowing as many as possible through, but blocking the rest until connections become free.


It can do this, but why would it be better than having golang itself do it?  It could make sense if you can push your "other connections" through pgbouncer, but can't push them through golang.  Then you can have one pooler that sees everything.  On the other hand, it introduce another layer of latency on every network round trip.  Plus another layer of complexity.