遇到一个题,要求分3个线程,每个调用一个方法,实现0 1 0 2 0 3 0 4 0 5...这样的标准输出。
自己翻翻资料,利用 threading package,threading.Lock.acquire() 和 threading.Lock.release() 方法,对某些关键变量访问的限制(锁),可以实现。
from atexit import register
import threading
import time
lock = threading.Lock()
class Series_Output(object):
def __init__(self, n: int):
self.n = n
self.i = 0
self.zero_flag = True
def zero(self):
while self.i <= self.n:
if self.zero_flag:
lock.acquire()
if True == self.zero_flag and self.i <= self.n:
if self.i < self.n:
print("0", end=" ")
self.zero_flag = False
self.i += 1
lock.release()
def even(self):
while self.i <= self.n:
if not self.zero_flag and self.i % 2 == 0:
lock.acquire()
if not self.zero_flag and self.i % 2 == 0 and self.i <= self.n:
print(self.i, end=" ")
self.zero_flag = True
lock.release()
def odd(self):
while self.i <= self.n:
if not self.zero_flag and self.i % 2 == 1:
lock.acquire()
if not self.zero_flag and self.i % 2 == 1 and self.i <= self.n:
print(self.i, end=" ")
self.zero_flag = True
lock.release()
@register
def exit():
print("\n所有线程执行完毕")
print(f"ending time: {time.perf_counter()}")
if __name__ == "__main__":
print(f"starting time: {time.perf_counter()}")
s = Series_Output(20)
threading.Thread(target=s.odd).start()
threading.Thread(target=s.even).start()
threading.Thread(target=s.zero).start()
结果如下。计算时间,可知需要5秒多。明显需要改进
starting time: 178479.0038989
0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 10 0 11 0 12 0 13 0 14 0 15 0 16 0 17 0 18 0 19 0 20
所有线程执行完毕
ending time: 178484.8257394
请教豆包AI, 提供了用threading.condition 这样比较高级的调用。
import time
from atexit import register
import threading
class SeriesOutput(object):
def __init__(self, n: int):
self.n = n
self.i = 0
self.zero_flag = True
self.lock = threading.Lock()
self.condition = threading.Condition(self.lock)
def zero(self):
while self.i <= self.n:
with self.condition:
while not self.zero_flag:
self.condition.wait(timeout=1)
if self.i <= self.n:
if self.i < self.n:
print("0", end=" ")
# print("0", end=" ")
self.zero_flag = False
self.i += 1
self.condition.notify_all()
def even(self):
while self.i <= self.n:
with self.condition:
while self.zero_flag or self.i % 2!= 0:
self.condition.wait(timeout=1)
if self.i <= self.n:
print(self.i, end=" ")
self.zero_flag = True
self.condition.notify_all()
def odd(self):
while self.i <= self.n:
with self.condition:
while self.zero_flag or self.i % 2!= 1:
self.condition.wait(timeout=1)
if self.i <= self.n:
print(self.i, end=" ")
self.zero_flag = True
self.condition.notify_all()
@register
def exit():
print("\n所有线程执行完毕")
if __name__ == "__main__":
# print(f"starting time: {time.perf_counter()}")
s = SeriesOutput(20)
try:
threading.Thread(target=s.odd).start()
threading.Thread(target=s.even).start()
threading.Thread(target=s.zero).start()
except Exception as e:
print(f"线程启动失败:{e}")
结果在屏幕上秒出,应该是毫秒级别的。但是程序却不退出。反复深入询问,都没有改善。
花了很多精力去troubleshooting, 也仔细阅读Python Threading 的文档,加了一下debug log,发现其实是 总是有个 odd 或 even 的进程还在等待,没有退出。锁已经是释放掉了,也试了主动在各处release()锁,python 都报错说没锁可以release。
调试的log输出代码为:
time.sleep(2)
print(f"\nFinally, s.i = {s.i}, s.zero_flag = {s.zero_flag}")
print(f"Current thread status is:\n"
f"threading.active_count() is {threading.active_count()};\n"
f"threading.enumerate() is {threading.enumerate()};\n"
f"s.lock.locked() is {s.lock.locked()}")
结果是
starting time: 194847.1397601
0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 10 0 11 0 12 0 13 0 14 0 15 0 16 0 17 0 18 0 19 0 20
Finally, s.i = 21, s.zero_flag = False
Current thread status is:
threading.active_count() is 2;
threading.enumerate() is [<_MainThread(MainThread, started 18344)>, <Thread(Thread-2 (even), started 12596)>];
s.lock.locked() is False
root cause 是 s.i 到达 s.n 值以后,even() 或者 odd() 方法中的一个while 判断条件总为True, 导致 s.condition.wait() 一直等待,相应进程就不退出。
改写后代码如下:
import time
from atexit import register
import threading
class SeriesOutput(object):
def __init__(self, n: int):
self.n = n
self.i = 0
self.zero_flag = True
self.lock = threading.Lock()
self.condition = threading.Condition(self.lock)
def zero(self):
while self.i <= self.n:
with self.condition:
while not self.zero_flag:
self.condition.wait()
if self.i <= self.n:
if self.i < self.n:
print("0", end=" ")
# print("0", end=" ")
self.zero_flag = False
self.i += 1
self.condition.notify_all()
def even(self):
while self.i <= self.n:
with self.condition:
while self.zero_flag or (self.i <= self.n and self.i % 2!= 0):
# while self.zero_flag or self.i % 2 != 0:
self.condition.wait()
if self.i <= self.n:
print(self.i, end=" ")
self.zero_flag = True
self.condition.notify()
def odd(self):
while self.i <= self.n:
with self.condition:
while self.zero_flag or (self.i <= self.n and self.i % 2!= 1):
# while self.zero_flag or self.i % 2 != 1:
self.condition.wait()
if self.i <= self.n:
print(self.i, end=" ")
self.zero_flag = True
self.condition.notify()
@register
def exit():
print("\n所有线程执行完毕")
print(f"ending time: {time.perf_counter()}")
if __name__ == "__main__":
print(f"starting time: {time.perf_counter()}")
s = SeriesOutput(20)
try:
threading.Thread(target=s.odd).start()
threading.Thread(target=s.even).start()
threading.Thread(target=s.zero).start()
except Exception as e:
print(f"线程启动失败:{e}")
# time.sleep(2)
# print(f"\nFinally, s.i = {s.i}, s.zero_flag = {s.zero_flag}")
# print(f"Current thread status is:\n"
# f"threading.active_count() is {threading.active_count()};\n"
# f"threading.enumerate() is {threading.enumerate()};\n"
# f"s.lock.locked() is {s.lock.locked()}")
结果如下。在5毫秒完成任务。比原始approach所需时间的1/1000。可以说,效率大大提高。
starting time: 195422.0831454
0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 10 0 11 0 12 0 13 0 14 0 15 0 16 0 17 0 18 0 19 0 20
所有线程执行完毕
ending time: 195422.0887467
看来LLM prompt + 人工手动优化确实是下一步的工作常态。