I coached a team that had similar challenges. The way that team solved that was to do the following:
* Towards the end of sprint planning, the dev team looked for any "critical paths" or "potential bottlenecks" in the Sprint plan. The team mark any tasks that met one of those two conditions with a red asterisk. People would generally start work on the critical path type tasks first(or early) so as to reduce the bottlenecks. The team would also keep a close eye on the critical path as the sprint progressed.
* (This team also did this)While I strongly prefer people to sign up for tasks as the sprint emerges, I think it's generally a good practice for developers, at the end of Sprint planning, to sign up for a task or two so everyone knows where everyone is starting.
In my experience, signing up for more than about two tasks early in the sprint usually creates more bottlenecks rather than less, as everyone views it as a static plan that cannot be changed. As such, people end up saying "Well, I can't work on task C because so and so is signed up for task B and he's still working on task X".
I have some more material on Sprint tasking that you might be interested in here: http://www.scrumcrazy.com/Sprint+Tasking+Tips