面试题答案
一键面试- 减少锁的粒度:
- 原理:尽量缩小锁保护的代码块范围,只对共享资源的关键操作加锁。这样在同一时间内,更多线程可以并行执行非关键代码部分,提高整体性能。
- 示例:
import threading
class Counter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()
def increment(self):
# 只对共享资源操作加锁
with self.lock:
self.value += 1
counter = Counter()
threads = []
for _ in range(10):
t = threading.Thread(target=counter.increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print(counter.value)
在这个例子中,increment
方法中只有 self.value += 1
这一涉及共享资源的操作被锁保护,而不是将整个方法都置于锁之下。
- 使用读写锁(
RLock
):- 原理:如果多线程程序中读操作远多于写操作,可以使用读写锁。多个线程可以同时进行读操作,只有写操作时才需要独占锁。这样读操作可以并行执行,提高了并发性能。
- 示例:
import threading
class Data:
def __init__(self):
self.value = 0
self.rw_lock = threading.RLock()
def read(self):
with self.rw_lock:
return self.value
def write(self, new_value):
with self.rw_lock:
self.value = new_value
data = Data()
read_threads = []
write_thread = threading.Thread(target=data.write, args=(10,))
for _ in range(5):
t = threading.Thread(target=data.read)
read_threads.append(t)
t.start()
write_thread.start()
for t in read_threads:
t.join()
write_thread.join()
这里 read
方法可以被多个线程同时调用,因为读操作不会修改共享资源,write
方法则会独占锁,保证写操作的原子性。
- 锁的预分配和复用:
- 原理:提前创建好锁对象并复用,避免在多线程运行过程中频繁创建和销毁锁,减少额外开销。
- 示例:
import threading
lock = threading.Lock()
def some_function():
with lock:
# 共享资源操作
pass
threads = []
for _ in range(10):
t = threading.Thread(target=some_function)
threads.append(t)
t.start()
for t in threads:
t.join()
这里 lock
对象在程序开始时就创建好了,所有线程复用该锁,而不是每个线程自己创建锁。
- 考虑无锁数据结构:
- 原理:使用一些线程安全的无锁数据结构,例如
queue.Queue
在Python中是线程安全的,不需要额外加锁。它内部使用了复杂的同步机制来保证线程安全,并且在某些场景下性能优于使用锁保护的普通数据结构。 - 示例:
- 原理:使用一些线程安全的无锁数据结构,例如
import threading
import queue
q = queue.Queue()
def producer():
for i in range(5):
q.put(i)
def consumer():
while not q.empty():
item = q.get()
print(f"Consumed {item}")
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
producer_thread.start()
consumer_thread.start()
producer_thread.join()
consumer_thread.join()
这里 queue.Queue
作为线程安全的数据结构,生产者和消费者线程可以直接操作它,而无需额外加锁。