AI Skill Report Card

Assessing Thread Safety

A-84·Aug 14, 2026·Source: Web
13 / 15

When asked "is this thread-safe?", check three things in order:

  1. Which interpreter build? GIL-enabled (default) vs free-threaded (--disable-gil / t suffix, PEP 703). Guarantees differ substantially.
  2. What's the operation category? Atomic bytecode op, container mutation, C-API call, or third-party C extension?
  3. What does CPython actually guarantee vs what "seems safe"? Many operations look atomic but aren't guaranteed across versions/builds.
Python
# Example triage import sys free_threaded = sys._is_gil_enabled is not None and not sys._is_gil_enabled() # dict.setdefault, list.append: atomic under GIL, but under free-threaded # builds only *individual* container ops are internally locked - # compound operations (check-then-act) are NEVER safe in either mode. counter = {} counter[key] = counter.get(key, 0) + 1 # NOT thread-safe in any mode
Recommendation
Add a concrete example showing a race condition manifesting with actual output/error (e.g., lost updates in a counter) rather than just describing safety verdicts.
13 / 15

Progress:

  • Identify interpreter mode (GIL vs free-threaded) the code must support
  • Classify each shared-state operation
  • Check against known-atomic operation list
  • Check C extensions for Py_mod_gil slot / explicit free-threading support
  • Flag compound/check-then-act patterns regardless of atomicity
  • Recommend explicit synchronization (threading.Lock, queue.Queue, immutable data) where guarantees are insufficient
  • Note version-specific caveats (behavior can change between minor versions; nothing here is a language-level contract unless documented)

1. Interpreter mode matters

  • GIL-enabled build: bytecode-level atomicity for single ops; the GIL serializes execution, giving many single opcode operations effective atomicity as a side effect (not a guarantee).
  • Free-threaded build (PEP 703, 3.13+): no GIL; CPython instead guarantees specific operations are safe via internal per-object locks (critical sections). Anything not explicitly documented as safe should be treated as unsafe.

2. Operation classification

  • Documented atomic: e.g., single container item get/set/del on dict, list, set for built-in types — safe from corrupting interpreter state, but does not mean no race conditions in program logic.
  • Compound operations: x += 1, if k not in d: d[k] = v, list.sort() interleaved with reads — never safe without a lock, in any mode.
  • Reference counting: under free-threaded builds, refcounting uses biased/deferred reference counting; safe by design, not something users manage directly.
  • C extensions: must opt in to free-threading support (Py_MOD_GIL_NOT_USED); otherwise CPython re-enables the GIL automatically when such a module is imported. Flag any extension without explicit multi-phase init support as unsafe to assume free-threaded-safe.

3. Never trust "looks atomic"

Even single bytecode instructions are an implementation detail unless explicitly documented in threadsafety-style guarantees. Recommend explicit locking for any correctness-critical shared mutable state.

Recommendation
Include a quick-reference table of known-atomic vs non-atomic operations for fast lookup instead of prose-only classification.
16 / 20

Example 1: Input: shared_list.append(x) called from multiple threads, free-threaded build, no lock. Output: Safe — list.append is a documented atomic operation protected by internal critical sections in free-threaded CPython. No external lock needed for this single call, but iterating and mutating from another thread concurrently still needs care (iteration is not itself atomic).

Example 2: Input:

Python
if key not in cache: cache[key] = expensive_compute(key)

Output: Not thread-safe in either GIL or free-threaded mode. This is a check-then-act compound operation — two threads can both pass the not in check before either writes. Recommend cache.setdefault(key, expensive_compute(key)) (still recomputes redundantly but avoids corruption) or a lock/functools.lru_cache with internal locking, or dict.setdefault combined with a sentinel + lock if compute is expensive.

Example 3: Input: A C extension module with no free-threading support, imported under python3.13t. Output: CPython will implicitly re-enable the GIL for the whole process (with a RuntimeWarning) when it detects the extension lacks the Py_mod_gil slot declaring support. Flag this as: (a) safe from corruption, but (b) silently defeats the purpose of running free-threaded — surface this to the user as a performance/architecture issue, not just a safety one.

Recommendation
Add a bad-outcome example where the evaluator incorrectly assumes safety, to illustrate common misjudgment patterns explicitly.
  • Default recommendation for shared mutable state: use threading.Lock/RLock or higher-level primitives (queue.Queue, concurrent.futures) rather than relying on interpreter internals.
  • Prefer immutable data structures or message-passing (queues) over shared mutable state — sidesteps the entire question.
  • When reviewing library code intended to support free-threaded Python, check for explicit test matrix entries (3.13t, 3.14t) and CI runs with PYTHON_GIL=0.
  • Cite the specific CPython version when giving atomicity guarantees — behavior is not a stable language guarantee, only what a given release documents/implements.
  • For C extensions, check for Py_mod_gil module slot and Py_MOD_GIL_NOT_USED usage as the marker of intentional free-threading support.
  • Assuming "the GIL makes everything thread-safe" — it only serializes bytecode execution, not multi-step logic.
  • Assuming free-threaded builds require locks for everything — many single container operations remain internally safe by design.
  • Treating undocumented "seems atomic" behavior as a guarantee across Python versions.
  • Forgetting that iteration over a container while another thread mutates it is unsafe even when individual get/set ops are safe.
  • Ignoring C extensions as a hidden source of GIL re-activation or actual data races when they lack free-threading support.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
13/15
Workflow
13/15
Examples
16/20
Completeness
15/20
Format
14/15
Conciseness
13/15