Keep your pure readers completely out of any transactions (running canned queries will automatically use snapshot transactions). That way they will never block any writers and will always see a consistent view of the database (READ COMMITTED). Run your UPDATE statements either without any active transaction (the single UPDATE statement will implicitly run in a normal SERIALIZABLE transaction this way) or if you have multiple UPDATE statements which belong together run them in a normal transaction together. Writers will block each other (as long as the transactions are short enough they'll just queue up and execute one after another) Writers will block readers for the split second it takes to update a few pointers inside the buffer manager during the actual COMMIT of a transaction (making sure that readers will only see completely committed transactions).
Snapshot transactions are read-only transactions that preserve the database's state at a given point in time; they take a "snapshot" of the database. The overall effect of this is that readers using snapshot transactions do not block writers. Throughput is increased and a consistent view of the data is provided.
Snapshot transactions are efficiently implemented within the server core. In order to use snapshot transactions, the developer simply specifies the transaction type when starting a transaction. Snapshot transactions are the default transaction type used for "select" SQL queries. They are also used to provide online backup support.