In my application I use the .NET SerialPort class for reading and writing data. The reading is done using the DataReceived event, I assume internally on a ThreadPool thread.
Here's a great thread on the topic, with the author of the SerialPort class participating:
MSDN: How does SerialPort handle DataReceived?
From my experience, I've written a dozen serial communication apps for use as hardware simulators, I don't lock. I didn't know at the time if I was safe or not, but in practice, I haven't had an error yet. (a year of near constant use by 20+ testers and automated test machines) That said, my applications don't leave the company, if I were writing apps for public consumption I might take more care.
From the documentation:
Any public static (Shared in Visual Basic) members of this type (SerialPort) are thread safe. Any instance members are not guaranteed to be thread safe.
So you should definetly synchronize your read/writes with locks.