AI Skill Report Card

Ensuring Memoryview Thread Safety

A-83·Aug 14, 2026·Source: Web
12 / 15
Python
import threading # Underlying mutable buffer shared across threads buf = bytearray(1024) mv = memoryview(buf) lock = threading.Lock() def worker(start, end, value): with lock: mv[start:end] = bytes([value]) * (end - start) threads = [threading.Thread(target=worker, args=(i*100, (i+1)*100, i)) for i in range(10)] for t in threads: t.start() for t in threads: t.join()

Key fact: on the standard GIL-enabled CPython build, individual memoryview operations (single-item get/set, slicing) are atomic at the bytecode level, but compound operations (read-modify-write, multi-step slice assignment) are not atomic and require explicit locking. On free-threaded builds (--disable-gil / PEP 703), even individual operations can race unless the memoryview's internal locking guarantees are understood — treat both builds the same way: assume nothing is atomic across multiple opcodes.

Recommendation
Quick Start example doesn't actually demonstrate a race condition or the atomicity nuance discussed later — a more illustrative example (e.g., read-modify-write) would land the point faster
14 / 15

Progress:

  • Step 1: Identify every memoryview/buffer-protocol object shared across threads
  • Step 2: Classify each access pattern as read-only, single-write, or read-modify-write
  • Step 3: Determine if the underlying object is mutable (bytearray, array.array, numpy array) or immutable (bytes)
  • Step 4: Add synchronization (Lock/RLock) around any compound or read-modify-write access
  • Step 5: Verify behavior under both the default GIL build and the free-threaded build if targeting 3.13+
  • Step 6: Check for use-after-free hazards — releasing a buffer while a memoryview into it is still live on another thread

Step 1 — Inventory shared objects Grep for memoryview(, .cast(, buffer(, and any object passed to threading.Thread args that supports the buffer protocol.

Step 2 — Classify access

  • Read-only across threads → generally safe, no lock needed.
  • Single atomic write (mv[i] = x) → safe under GIL builds for single-element assignment; still verify on free-threaded builds.
  • Read-modify-write (mv[i] += 1, slice assignment computed from current contents) → always needs a lock.

Step 3 — Mutability of backing store

  • bytes objects backing a memoryview are immutable — no data race possible, but the memoryview object itself (its state: shape, released flag) can still race.
  • bytearray, array.array, mmap, numpy buffers — mutable, so concurrent writes need locks.

Step 4 — Add synchronization Wrap compound operations in threading.Lock. Prefer one lock per logical buffer, not a global lock, to avoid contention.

Step 5 — Verify across builds Free-threaded CPython removes the GIL, so operations that were "accidentally atomic" under GIL (e.g., single bytecode ops) may no longer be. Test with python3.13t or the --disable-gil build if supporting it.

Step 6 — Lifetime hazards Calling .release() on a memoryview or letting the exporting object be garbage collected while another thread holds a view into it causes undefined behavior or crashes. Ensure the exporting object outlives all views, and don't release a shared memoryview from one thread while another is using it — protect release with the same lock.

Recommendation
Add a third example showing a free-threaded-specific failure mode (e.g., benchmark/output diff between GIL and no-GIL builds) since that's a key differentiator claimed in the description
16 / 20

Example 1: Input:

Python
counter_buf = bytearray(8) mv = memoryview(counter_buf).cast('q') # int64 view def increment(): mv[0] += 1 # read-modify-write, NOT atomic

Output (fixed):

Python
counter_buf = bytearray(8) mv = memoryview(counter_buf).cast('q') lock = threading.Lock() def increment(): with lock: mv[0] += 1

Reasoning: mv[0] += 1 compiles to separate LOAD/ADD/STORE steps — a race condition on any build, GIL or not.

Example 2: Input:

Python
data = bytearray(range(256)) mv = memoryview(data) def reader(): return bytes(mv) # full read def releaser(): mv.release()

Output (fixed): Guard release with the same lock used for reads, and only release once no other thread holds a reference:

Python
lock = threading.Lock() def reader(): with lock: return bytes(mv) def releaser(): with lock: mv.release()

Reasoning: releasing a memoryview while another thread is mid-read causes a BufferError or crash; synchronize lifetime operations, not just data mutation.

Recommendation
Some redundancy between Workflow step explanations and Best Practices/Pitfalls sections — could tighten by cross-referencing instead of restating
  • Default to one Lock per shared buffer; avoid sharing a single global lock across unrelated buffers.
  • Treat +=, slice-assignment-from-self, and any multi-step read-then-write as compound — always lock these.
  • Never assume GIL-build atomicity guarantees carry over to free-threaded builds — write code as if no GIL exists.
  • Keep the exporting object (the bytearray/array/mmap) alive at least as long as any memoryview into it across all threads.
  • Prefer immutable snapshots (bytes(mv)) for cross-thread reads when the buffer is being concurrently mutated, rather than reading through the live view.
  • When targeting both GIL and free-threaded builds, test explicitly on python3.13t (or later free-threaded builds) under thread-sanitizer-style stress tests (many iterations, threading.Barrier to maximize contention).
  • Assuming a single memoryview element assignment is always safe just because "it's one line of Python" — check the actual bytecode/operation count.
  • Forgetting that .cast() creates a new memoryview sharing the same underlying buffer — locks must cover all cast views, not just the original.
  • Releasing or letting a buffer's exporter get garbage-collected while a background thread still holds a memoryview into it.
  • Using a global interpreter-wide lock for unrelated buffers, creating unnecessary contention and negating the performance benefit of free-threading.
  • Porting GIL-era code to free-threaded builds without re-auditing every "looked atomic" operation.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
12/15
Workflow
14/15
Examples
16/20
Completeness
17/20
Format
15/15
Conciseness
13/15