This is a subtlety that eluded me for a while. GOMAXPROCS is the number of running threads, not the total number of threads. You could have 30 threads, but if they're all blocked waiting on IO, none of them count against GOMAXPROCS. (Right?)
So if I understand correctly: A goroutine locked to a thread only counts against GOMAXPROCS while it's running. If it's waiting on IO (or a channel or not running for some other reason), then it doesn't. But this is the same as any other goroutine. Which is why being locked to a thread is (as you said) independent of GOMAXPROCS.
Finally, this implies (well, it says outright, if I understand correctly) that even if GOMAXPROCS == 1, you could have multiple threads, but only one should be running at any given time. So if one goroutine does some blocking IO, and other goroutines are runnable, the runtime may well start a new thread to handle them, and then quiesce it (or whatever the appropriate term is) when the first one is runnable again.
(Right?)
-- Larry