1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
| import time
import uuid
import threading
from queue import Queue
from loguru import logger
class ThreadId(threading.local):
"""线程本地存储,每个线程有自己独立的id"""
id: str | None = None
thread_id = ThreadId()
class FIFOThreadLock:
def __init__(self, max_concurrent=1):
"""
初始化FIFO线程锁(支持可重入)
Args:
max_concurrent: 最大并行执行的线程数量,默认为1(相当于普通锁)
"""
self._queue = Queue()
self._lock = threading.Lock()
self._max_concurrent = max_concurrent
self._current_count = 0
# 记录哪些线程已经获得了锁(用于防止重复release)
self._acquired_threads = set()
# ✅ 新增:记录每个线程的重入次数
self._reentrant_count = {}
def _get_thread_id(self):
"""获取当前线程的唯一标识符"""
if not thread_id.id:
thread_id.id = str(uuid.uuid4())
return thread_id.id
def acquire(self, blocking=True):
"""获取锁(支持可重入)"""
tid = self._get_thread_id()
# ✅ 检查是否是重入调用
with self._lock:
if tid in self._acquired_threads:
# 同一线程重入,只增加计数
self._reentrant_count[tid] = self._reentrant_count.get(tid, 1) + 1
logger.debug(f"线程 {tid} 重入,当前重入次数: {self._reentrant_count[tid]}")
return True
# 创建一个事件用于通知当前线程
event = threading.Event()
with self._lock:
# 将事件和线程ID关联放入队列
self._queue.put((event, tid))
# 检查是否可以立即执行
can_proceed = self._current_count < self._max_concurrent
if can_proceed:
self._current_count += 1
self._acquired_threads.add(tid)
self._reentrant_count[tid] = 1 # ✅ 初始化重入计数
event.set()
# 等待轮到自己
if blocking:
event.wait()
# 如果事件被设置,但当前线程不在acquired_threads中
# 说明是在release()中被唤醒的,需要添加到集合中
with self._lock:
if tid not in self._acquired_threads:
self._acquired_threads.add(tid)
self._reentrant_count[tid] = 1 # ✅ 初始化重入计数
return True
else:
# 非阻塞模式
is_acquired = event.is_set()
if not is_acquired:
# 如果没有获得锁,需要从队列中移除并清理
with self._lock:
# 需要遍历队列找到对应的事件并移除
temp_items = []
found = False
while not self._queue.empty():
item = self._queue.get()
if item[0] is event and not found:
found = True
continue
temp_items.append(item)
# 将其他项放回队列
for item in temp_items:
self._queue.put(item)
else:
# ✅ 非阻塞模式成功获取锁,初始化重入计数
with self._lock:
if tid not in self._reentrant_count:
self._reentrant_count[tid] = 1
return is_acquired
def release(self):
"""释放锁(支持可重入)"""
tid = self._get_thread_id()
with self._lock:
# 检查当前线程是否真的持有锁
if tid not in self._acquired_threads:
raise RuntimeError(f"线程 {tid} 试图释放未持有的锁")
# ✅ 检查重入次数
reentrant_count = self._reentrant_count.get(tid, 1)
if reentrant_count > 1:
# 还有重入层级,只减少计数,不真正释放
self._reentrant_count[tid] = reentrant_count - 1
logger.debug(f"线程 {tid} 减少重入计数,剩余: {self._reentrant_count[tid]}")
return
# ✅ 重入计数为1,执行真正的释放
# 移除当前线程的标记
self._acquired_threads.remove(tid)
del self._reentrant_count[tid] # ✅ 清理重入计数
# 从队列中找到并移除当前线程的事件
found = False
temp_items = []
while not self._queue.empty():
item = self._queue.get()
if item[1] == tid and not found:
# 找到了当前线程,不放回队列
found = True
logger.debug(f"[DEBUG] 线程 {tid} 从队列中移除")
else:
temp_items.append(item)
# 将其他项放回队列
for item in temp_items:
self._queue.put(item)
if not found:
logger.debug(f"[WARNING] 线程 {tid} 在队列中未找到!")
# 减少当前计数(使用max确保不会变成负数)
self._current_count = max(0, self._current_count - 1)
logger.debug(
f"[DEBUG] 释放后计数: {self._current_count}, 队列大小: {self._queue.qsize()}"
)
# 通知下一个等待的线程(如果有空位)
while (
not self._queue.empty() and self._current_count < self._max_concurrent
):
next_item = self._queue.queue[0]
next_event, next_thread_id = next_item
if not next_event.is_set():
self._current_count += 1
logger.debug(
f"[DEBUG] 唤醒线程 {next_thread_id}, 新计数: {self._current_count}"
)
next_event.set()
break
else:
# 如果事件已经设置,从队列中移除
logger.debug(f"[DEBUG] 跳过已设置的事件 {next_thread_id}")
self._queue.get()
break
def __enter__(self):
self.acquire()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.release()
return False
# ============ 测试代码 ============
def test_basic_usage():
"""测试基本功能"""
logger.info("=== 测试1: 基本功能(3并发) ===\n")
fifo_lock = FIFOThreadLock(max_concurrent=3)
def worker(thread_id):
logger.info(f"线程 {thread_id} 正在等待")
with fifo_lock:
logger.info(f"✓ 线程 {thread_id} 获得了锁,开始执行")
time.sleep(1)
logger.info(f"✓ 线程 {thread_id} 完成工作")
threads = []
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
time.sleep(0.1)
for t in threads:
t.join()
logger.info(f"\n最终计数: {fifo_lock._current_count} (应该为0)")
logger.info(f"持有锁的线程: {fifo_lock._acquired_threads} (应该为空)\n")
def test_reentrant():
"""✅ 测试可重入功能(修复的关键)"""
logger.info("=== 测试2: 可重入功能 ===\n")
fifo_lock = FIFOThreadLock(max_concurrent=2)
def nested_worker(worker_id):
logger.info(f"Worker {worker_id} - 第一层获取锁")
with fifo_lock:
logger.info(f"✓ Worker {worker_id} - 第一层成功")
time.sleep(0.2)
logger.info(f"Worker {worker_id} - 第二层获取锁(嵌套)")
with fifo_lock:
logger.info(f"✓ Worker {worker_id} - 第二层成功(可重入)")
time.sleep(0.2)
logger.info(f"Worker {worker_id} - 第三层获取锁(嵌套)")
with fifo_lock:
logger.info(f"✓ Worker {worker_id} - 第三层成功(可重入)")
time.sleep(0.2)
logger.info(f"✓ Worker {worker_id} - 第三层释放")
logger.info(f"✓ Worker {worker_id} - 第二层释放")
logger.info(f"✓ Worker {worker_id} - 第一层释放")
threads = []
for i in range(3):
t = threading.Thread(target=nested_worker, args=(i,))
threads.append(t)
t.start()
time.sleep(0.05)
for t in threads:
t.join()
logger.info(f"\n最终计数: {fifo_lock._current_count} (应该为0)")
logger.info(f"持有锁的线程: {fifo_lock._acquired_threads} (应该为空)")
logger.info(f"重入计数: {fifo_lock._reentrant_count} (应该为空)\n")
def test_multiple_acquire():
"""测试线程多次获取和释放锁"""
logger.info("=== 测试3: 线程多次获取释放锁 ===\n")
fifo_lock = FIFOThreadLock(max_concurrent=2)
def worker(worker_id, iterations):
for i in range(iterations):
logger.info(f"Worker {worker_id} - 第{i+1}次尝试获取锁")
with fifo_lock:
logger.info(f"✓ Worker {worker_id} - 第{i+1}次成功获取锁")
time.sleep(0.3)
logger.info(f"✓ Worker {worker_id} - 第{i+1}次释放锁")
threads = []
for i in range(3):
t = threading.Thread(target=worker, args=(i, 3))
threads.append(t)
t.start()
for t in threads:
t.join()
logger.info(f"\n最终计数: {fifo_lock._current_count} (应该为0)")
logger.info(f"持有锁的线程: {fifo_lock._acquired_threads} (应该为空)\n")
def test_exception_handling():
"""测试异常处理"""
logger.info("=== 测试4: 异常处理 ===\n")
fifo_lock = FIFOThreadLock(max_concurrent=2)
def bad_worker(thread_id):
logger.info(f"线程 {thread_id} (会抛异常) 正在等待")
try:
with fifo_lock:
logger.info(f"线程 {thread_id} 获得了锁,即将抛出异常")
raise ValueError("模拟异常")
except ValueError:
logger.info(f"✓ 线程 {thread_id} 捕获了异常,锁已自动释放")
def normal_worker(thread_id):
logger.info(f"线程 {thread_id} (正常) 正在等待")
with fifo_lock:
logger.info(f"✓ 线程 {thread_id} 获得了锁")
time.sleep(0.5)
logger.info(f"✓ 线程 {thread_id} 完成工作")
threads = []
for i in range(5):
if i == 2:
t = threading.Thread(target=bad_worker, args=(i,))
else:
t = threading.Thread(target=normal_worker, args=(i,))
threads.append(t)
t.start()
time.sleep(0.1)
for t in threads:
t.join()
logger.info(f"\n最终计数: {fifo_lock._current_count} (应该为0)")
logger.info(f"持有锁的线程: {fifo_lock._acquired_threads} (应该为空)\n")
if __name__ == "__main__":
test_basic_usage()
logger.info("\n" + "=" * 60 + "\n")
test_reentrant()
logger.info("\n" + "=" * 60 + "\n")
test_multiple_acquire()
logger.info("\n" + "=" * 60 + "\n")
test_exception_handling()
logger.info("\n所有测试完成!")
|