an exception `serial.SerialException` can be thrown by `self.readline()` (called by `self._read()` ->`self._read_with_retry()` which leaves the lock unreleased and causes a deadlock within the system.
an exception `serial.SerialException` can be thrown by `self.readline()` (called by `self._read()` ->`self._read_with_retry()` which leaves the lock unreleased and causes a deadlock within the system.

It is much safer to use locks in this way so that you don't have to think about all of the exception cases -- the context management of the lock (`__enter__` and `__exit__` functions) will automatically acquire and release the lock for you no matter the way the code leaves that block.

It would be prudent to also change the function `write_check_freq` to use the context management for a lock too - although I haven't done it yet I'd imagine something like..

```
def write_check_freq(self, cmd_str, delay_write, lock_=False):
    if lock_:
        self._write_check_freq_locked()
    else:
        self._write_check_freq_unlocked()

def _write_check_freq_locked():
    with self._lock:
        self._write_check_freq_unlocked()
```

JaneliaSciComp/serial_device_python