午睡醒来时灵光一现,突然想到这个问题的解决方案,特此记录。
问题描述
在处理大型项目时,我遇到了一个棘手的架构问题。项目中的关键机制是:每次点击按钮都会创建一个长期运行的线程,而频繁的按钮点击会导致大量线程并行运作。这种设计带来了两个严重问题:一是公共资源的并发修改问题,二是某些需要独占的程序资源出现错误。
起初,我尝试了简单的加锁和异步方案,但都未能完美解决问题。加锁方案存在明显缺陷:每次创建新线程时都需要对旧线程解锁并给新线程加锁。更糟糕的是,当新线程运行结束而尚未创建下一个新线程时,旧线程会自动解除阻塞继续运行,这与预期行为不符。最终,我找到了一种更简便高效的解决方案。
例子展示
首先,我们先来看一个很简单的例子,代码如下
import threading
import time
def test(thread_count):
global king_finance
king_finance = king_finance / thread_count
for i in range(500):
print(f"这是第{thread_count}个线程的第{i+1}个数,皇帝的资产为{king_finance}")
def maintain():
time.sleep(10)
if __name__ == '__main__':
king_finance = 1000000000
test_thread = threading.Thread(target=test, args=(1,), daemon=True)
test_thread.start()
test_thread.join(timeout=0.01)
test_thread = threading.Thread(target=test, args=(2,), daemon=True)
test_thread.start()
test_thread.join(timeout=0.01)
# 保证主线程不结束
maintain()
运行结果的部分截图如下
由图可以看到,在超时时间timeout=0.01过后,线程test_thread和线程test+thread2会开始并发执行,很清楚的可以看到,如果没有线程2,线程1的皇帝的资产会一直都是1000000000,而当线程2与其并发执行的时候,将皇帝的资产修改为了500000000,直接减少了一半,这样就会导致,我在线程一运行的途中资源被线程2修改了。
其次还有一个问题是,由于实际需要,我创建第二个调用同一个函数的线程时,并不希望前一个这个线程在继续运行,继续执行语句中的非共享资源,引起界面混乱。
但当我实现了一个简易的锁来解决这个问题时,但是只能解决共享的资源在上锁时不能被其他线程所修改,并不能使他们只能其中一个来访问该函数中的其他非共享资源,代码如下
import threading
import time
def test(thread_count, lock):
lock.acquire()
global king_finance
king_finance = king_finance / thread_count
for i in range(500):
print(f"这是第{thread_count}个线程的第{i+1}个数,皇帝的资产为{king_finance}")
def maintain():
time.sleep(10)
if __name__ == '__main__':
lock = threading.Lock()
king_finance = 1000000000
test_thread = threading.Thread(target=test, args=(1, lock, ), daemon=True)
test_thread.start()
test_thread.join(timeout=0.01)
if lock.locked():
lock.release()
test_thread = threading.Thread(target=test, args=(2, lock, ), daemon=True)
test_thread.start()
test_thread.join(timeout=0.01)
# 保证主线程不结束
maintain()
运行结果如下
由图可知,确实可以有效的防止其他线程来同时修改公共资源,
但是并不能阻止旧线程访问该函数的非共享资源,这并不是我想要的结果。但是利用好线程id就能非常完美的解决我的问题
修改代码如下
import threading
import time
def test(thread_count, lock):
lock.acquire()
global king_finance, thread1_id, thread2_id
king_finance = king_finance / thread_count
for i in range(500):
if thread2_id:
if threading.current_thread().native_id == thread2_id:
print(f"这是第{thread_count}个线程的第{i+1}个数,皇帝的资产为{king_finance}")
else:
print(f"这是第{thread_count}个线程的第{i+1}个数,皇帝的资产为{king_finance}")
def maintain():
time.sleep(10)
if __name__ == '__main__':
lock = threading.Lock()
thread2_id = 0
thread1_id = 0
king_finance = 1000000000
test_thread = threading.Thread(target=test, args=(1, lock, ), daemon=True)
thread1_id = test_thread.native_id
test_thread.start()
test_thread.join(timeout=0.01)
if lock.locked():
lock.release()
test_thread = threading.Thread(target=test, args=(2, lock, ), daemon=True)
thread2_id = test_thread.native_id
test_thread.start()
test_thread.join(timeout=0.01)
# 保证主线程不结束
maintain()
其中结合了锁和一些逻辑,如果有新的线程建立,就只让新线程进入,其他的旧线程会直接退出,很好的实现了我所想要的效果。