Python
TypeError
Threading
Pickling
Error Handling

TypeError can't pickle _thread.RLock objects

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

TypeError: can't pickle _thread.RLock objects appears when Python tries to serialize an object graph that contains a reentrant lock. The lock itself is not meaningful outside the current process and thread state, so modules such as multiprocessing and concurrent.futures.ProcessPoolExecutor cannot pickle it and send it elsewhere.

Why Pickling Fails

Pickling works for data that can be serialized into bytes and reconstructed later. A _thread.RLock is a live synchronization primitive tied to runtime state, not just data.

That is why code like this fails:

python
1import threading
2from concurrent.futures import ProcessPoolExecutor
3
4
5class Worker:
6    def __init__(self):
7        self.lock = threading.RLock()
8        self.value = 42
9
10
11def run(worker):
12    return worker.value
13
14
15if __name__ == "__main__":
16    worker = Worker()
17    with ProcessPoolExecutor() as pool:
18        print(pool.submit(run, worker).result())

The Worker instance contains self.lock, so sending it to another process requires pickling that lock. Python refuses because the lock cannot be reconstructed meaningfully.

Where This Usually Happens

This error often appears indirectly. You may not be pickling a lock on purpose. Instead, you are pickling an object that contains one.

Common examples include:

  • class instances with threading.Lock or threading.RLock fields
  • objects holding loggers, queues, or clients that embed locks internally
  • bound methods where self contains an unpicklable lock

That last case surprises people often. Sending obj.method to a process pool also sends obj.

The Best Fix: Do Not Send the Lock

The cleanest fix is to keep synchronization primitives local to the process that owns them and pass only serializable data to worker processes.

python
1from concurrent.futures import ProcessPoolExecutor
2
3
4def run(value):
5    return value
6
7
8if __name__ == "__main__":
9    with ProcessPoolExecutor() as pool:
10        print(pool.submit(run, 42).result())

If the worker really needs a lock, create it inside that process instead of serializing one from the parent.

Rebuilding the Lock with __getstate__

If you need the class to remain picklable, exclude the lock from serialized state and recreate it on unpickle.

python
1import threading
2import pickle
3
4
5class SafeWorker:
6    def __init__(self, value):
7        self.lock = threading.RLock()
8        self.value = value
9
10    def __getstate__(self):
11        state = self.__dict__.copy()
12        state.pop("lock", None)
13        return state
14
15    def __setstate__(self, state):
16        self.__dict__.update(state)
17        self.lock = threading.RLock()
18
19
20obj = SafeWorker(42)
21data = pickle.dumps(obj)
22restored = pickle.loads(data)
23print(restored.value)

This works when the lock protects local in-process state and does not need to preserve ownership or lock count across serialization.

Threads and Processes Are Different Problems

Sometimes the presence of an RLock is a clue that you actually want threads, not processes. Threads share memory inside one process, so you do not need to pickle the object graph just to execute code concurrently.

If the workload is I/O-bound and already structured around shared objects with locks, a thread pool may be more natural than a process pool.

Common Pitfalls

The biggest pitfall is looking only at your own code and missing that a nested dependency contains the lock. Logging objects, sessions, and framework components often carry synchronization primitives internally.

Another issue is passing instance methods to a process pool. That silently serializes self, which may drag an RLock along with it.

Developers also sometimes try to serialize the lock intentionally, thinking it will preserve coordination across processes. It will not. Cross-process synchronization needs different primitives or architecture.

Finally, __getstate__ is useful, but do not use it to hide deeper design problems. If the object's meaning depends on live thread state, making it picklable may not actually make it safe.

Summary

  • '_thread.RLock objects cannot be pickled because they represent live synchronization state, not serializable data.'
  • The error usually means some larger object being sent to another process contains a lock.
  • The cleanest fix is to pass plain data instead of lock-bearing objects.
  • If appropriate, exclude the lock from pickled state and recreate it in __setstate__.
  • Reconsider whether you need processes at all if the design is heavily based on shared in-memory objects and locks.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.