I’ve done this before, long time ago, so I may not remember correctly and hardware may behave differently nowadays. Off the top of my head…
Make sure you call glFinish so that everything is properly rendered and ready before you read it back. Swapbuffers supposedly does this (IIRC causes the well known pipeline stall), and glReadpixels seems to call glFinish under-the-hood with most drivers, but you never know (glFinish appears to be there for a reason).
By the way, I don’t think v-sync has any implications in this case, since we’re not calling Swapbuffers.
Ensure a double buffered context as mentioned by others.
Pixel ownership is a big issue. If the window is partially obscured by another overlapping window (or dropdown menus from the application), the pixels under that region may be black or typically garbage and not what you intended to render there. For picking/selection this is typically not an issue, but it might be for area selection, screengrabs, radiosity lightmap rendering, texture baking and other fancy stuff.
I should note that on nVidia hardware with latest drivers, pixels are actually rasterized proper (the spec allows this), but don’t rely on this behavior. In combination with FSAA and glViewport calls, you may still get garbage (or on nVidia, pixels that fail pixelownership test have the gray window background color).
Use PBO or FBO if you want to be sure you get pixels on offscreen surfaces. I’ve found PBO’s easier for non-realtime critical image feedback stuff since they are easier to set up than FBO’s, but if you want total control and maximum hardware acceleration, go for FBO. PBO support may also have diminished quality in modern drivers due to neglect, as FBO’s are supposed to replace them.
Ensure that the drivers don’t force full screen anti-aliasing and transparancy anti-aliasing (alpha-to-coverage) if you’re using alpha-testing. This will obviously screw up your colors and thus indices!
Also, the OpenGL spec still doesn’t (AFAIK) guarantee pixel accuracy, both color and raster position.
As for rasterization position accuracy, for single-click picking, read back 3x3 pixels to ensure you always catch 1x1 pixels (e.g. for vertex selection). While on the subject, use glPointSize/glLineWidth to make it easier to catch vertices/lines. Bonus points if you push back occluding geometry with glPolygonOffset (so that lines and vertices don’t z-fight with polygons) to make vertex/edge selection easier for the user.
When you do color picking, allow some margin. E.g. RGB 25/25/25 to RGB 27/27/27 correspond to the same index, rather than exactly RGB 26/26/26). Color picking will likely fail if the user has desktop bit depth set to 16-bit. With some effort you can adapt the color margin to the bit depth, this will greatly reduce the range of indices you can use, though multi-pass rendering can be a solution (yikes!). If you only have a handful of objects/triangles/vertices, this should not be an issue though.
Watch out for rounding errors when doing the RGB<–>index conversion. Assume the colors will be slightly off (margin!), and always do a bounds check on the readback index result to catch index-out-of-bounds errors.
If you’re smart you will do a conformance test when the application starts, and notify the user if the bit-depth or pixel accuracy fails acceptable standards. Ideally, display this along with the GL_VENDOR string so that the user can report to the manufacturer that they need to fix their crappy drivers (cough Intel).
Ultimately, picking/selection is best done with pure software rendering/plain math calculations, but if you take care of the things I mentioned it should be fairly reliable.
Hope that helps.