多个线程可以共享一个列表 (list),但要注意并发访问时可能引发的问题
文章目录
- 线程安全的List
- 线程安全的Set
- 线程安全的 Dict
线程安全的List
- 以下实现了一个行为类似
list
的类, 并且在多线程环境下安全 - 这个类提供了与
list
类似的功能,同时确保了线程安全。这对于在多线程环境中使用列表而又避免数据竞争非常有用。
import threadingclass ThreadSafeList:def __init__(self):self._list = []self._lock = threading.Lock()def append(self, item):with self._lock:self._list.append(item)def extend(self, items):with self._lock:self._list.extend(items)def insert(self, index, item):with self._lock:self._list.insert(index, item)def remove(self, item):with self._lock:self._list.remove(item)def pop(self, index=-1):with self._lock:return self._list.pop(index)def clear(self):with self._lock:self._list.clear()def index(self, item):with self._lock:return self._list.index(item)def count(self, item):with self._lock:return self._list.count(item)def sort(self, *, key=None, reverse=False):with self._lock:self._list.sort(key=key, reverse=reverse)def reverse(self):with self._lock:self._list.reverse()def __getitem__(self, index):with self._lock:if isinstance(index, slice):return self._list[index.start:index.stop:index.step]else:return self._list[index]def __setitem__(self, index, value):with self._lock:self._list[index] = valuedef __delitem__(self, index):with self._lock:del self._list[index]def __len__(self):with self._lock:return len(self._list)def __iter__(self):with self._lock:return iter(self._list.copy())
在这个 ThreadSafeList
类中:
- 我们用
threading.Lock
来确保线程安全。 - 大多数
list
方法都得到了实现,包括append
、extend
、insert
、remove
、pop
、clear
、index
、count
、sort
、reverse
等。 __getitem__
和__setitem__
等方法也被实现,以支持索引和切片操作。__iter__
返回列表的副本,以防止迭代时出现竞争。
在
__iter__
方法中,返回列表的副本是为了确保迭代过程中不受其他线程影响,因此使用浅拷贝(shallow copy)就足够了。
- 浅拷贝会创建一个新的对象,该对象的内容与原始对象相同,但是内部元素的引用仍然指向原始对象中的元素。因此,如果对这些元素进行修改,原始对象和副本都会受到影响。
- 但是在这种情况下,因为我们已经在
__iter__
方法中使用了锁来确保线程安全,所以在迭代过程中不会出现同时修改的情况,因此返回列表的浅拷贝就足够了。- 如果返回的是深拷贝(deep copy),则会完全复制原始列表及其内部元素,这可能会带来额外的开销,并且在这个情况下并不是必要的。
线程安全的Set
Python 的内置 set
对象本身并不是线程安全的。虽然个别操作(如添加和删除单个元素)可能在底层是原子性的,但多个线程同时操作 set
时可能会导致数据竞争或不一致的结果。因此,在多线程环境中同时操作 set
时,需要额外的同步机制来确保线程安全。
常见的实现方式包括:
- 锁(Lock):使用
threading.Lock
来保护set
,确保在操作set
时没有其他线程干扰。例如,将set
的所有访问都包裹在with
语句中使用锁。 - 线程安全的集合:自己编写一个线程安全的集合类,类似于我之前示范的
ThreadSafeList
。这个类将set
的所有操作用锁来保护。
示例代码:
import threadingclass ThreadSafeSet:def __init__(self):self._set = set()self._lock = threading.Lock()def add(self, item):with self._lock:self._set.add(item)def remove(self, item):with self._lock:self._set.remove(item)def discard(self, item):with self._lock:self._set.discard(item)def __contains__(self, item):with self._lock:return item in self._setdef __iter__(self):with self._lock:return iter(self._set.copy())
在这个示例中,ThreadSafeSet
类使用 threading.Lock
来确保所有操作是线程安全的。我们使用浅拷贝来获取 set
的副本,以防止迭代期间的竞争。
总之,如果您在多线程环境中需要使用 set
,建议使用锁或类似的同步机制来确保线程安全。
线程安全的 Dict
在 Python 3 中,原生的 dict 类型本身不是线程安全的。
虽然在读操作时通常不会出现问题,但在写操作(包括更新、删除和插入)时,多个线程同时操作一个 dict 对象可能导致数据不一致或引发竞争条件。
因此,建议在多线程环境中对 dict 对象的访问进行同步控制。
class ThreadSafeDict:def __init__(self):self._lock = threading.Lock()self._dict = {}def set(self, key, value):with self._lock:self._dict[key] = valuedef get(self, key, default=None):with self._lock:return self._dict.get(key, default)def delete(self, key):with self._lock:if key in self._dict:del self._dict[key]def keys(self):with self._lock:return list(self._dict.keys())def values(self):with self._lock:return list(self._dict.values())def items(self):with self._lock:return list(self._dict.items())def update(self, other):with self._lock:self._dict.update(other)def clear(self):with self._lock:self._dict.clear()def __getitem__(self, key):with self._lock:return self._dict[key]def __setitem__(self, key, value):with self._lock:self._dict[key] = valuedef __delitem__(self, key):with self._lock:del self._dict[key]def __contains__(self, key):with self._lock:return key in self._dict