Prompt
Use the lock to protect shared mutable state during mutations, but avoid holding it for read-side convenience/field-by-field getters.
Rules
- Lock only for writes: Acquire the mutex only around code that mutates shared collections/fields (e.g.,
logs[],updates[],notifications[]). - Minimize critical sections: Do expensive or deterministic work (masking, truncation, formatting) outside the lock; lock only to commit the final updates.
- Reads: prefer no lock or atomic snapshots:
- If your read is “best-effort” and does not require a logically consistent multi-field view, avoid locking.
- If you need a consistent snapshot across multiple related reads, either:
- lock the whole read sequence, or
- copy the needed data under lock, then release and compute/iterate outside.
Example pattern (commit under lock, transform outside)
def update_item(self, *, no: int, heading: str | None, content: str | None):
# Preprocess outside the lock
if heading is not None:
new_heading = _truncate_heading(self._mask_recursive(heading))
if content is not None:
item_type = self.logs[no].type # or pass in required type explicitly
new_content = _truncate_content(self._mask_recursive(content), item_type)
with self._lock:
item = self.logs[no]
if heading is not None:
item.heading = new_heading
if content is not None:
item.content = new_content
self.updates.append(item.no)
Applying this standard will reduce lock contention, prevent unnecessary refactors tied to read-side locking, and still keep shared writes thread-safe.