线程Timer定时调用函数失效问题及MUTEX同步实现咨询
嘿,先帮你揪出Timer失效的核心原因:你在创建Timer的时候直接写了storeIntoSQL(names,unknown_encoding),这相当于立刻执行了这个存储函数,而不是把函数本身传给Timer去延迟执行。Timer需要的是函数对象,以及对应的参数,不是函数执行后的结果哦。
一、先把Timer给修好
把你的Timer代码改成下面这样就正常了:
from threading import Timer # 正确传递函数对象,用args传位置参数 timer = Timer(60, storeIntoSQL, args=(names, unknown_encoding)) timer.start()
这样Timer才会乖乖等60秒再调用存储函数,而不是一上来就执行。
不过这里还有个小问题:你硬设60秒延迟不一定匹配实际的学习耗时啊?万一学习只花了10秒,那还要等50秒;如果学习花了70秒,Timer提前触发了就麻烦了。更合理的做法是等学习过程完全结束后,再启动存储的线程,而不是靠固定时间猜。
二、更靠谱的异步执行方案(替代Timer)
既然你不想阻塞主线程,又要保证学习完再存储,直接用threading.Thread来处理每个样本的学习+存储就好,或者把存储逻辑放到学习完成后的子线程里:
方案1:每个样本单独开线程处理
这样主线程可以继续跑其他任务,完全不会被阻塞:
import threading def process_and_store(name_str, image_data, current_id): # 先完成学习过程(就是你原来的face_recognition那段逻辑) img_path = f'images/Output{current_id}.jpg' with open(img_path, "wb") as output_file: output_file.write(image_data) unknown_image = face_recognition.load_image_file(img_path) unknown_encoding = face_recognition.face_encodings(unknown_image)[0] # 学习完直接执行存储,不用等固定时间 storeIntoSQL(name_str, unknown_encoding) # 主逻辑里调用 with myConnection: cur = myConnection.cursor() cur.execute("Select name, image FROM images WHERE state = 1") rows = cur.fetchall() id = 0 for row in rows: print("%s, %s" % (row[0], row[1])) # 给每个样本开个线程处理,把当前id传进去避免全局变量冲突 thread = threading.Thread(target=process_and_store, args=(row[0], row[1], id)) thread.start() id += 1
这样每个样本的学习和存储都在独立线程里完成,主线程该干嘛干嘛,完全不耽误。
三、MUTEX(互斥锁)的具体实现
你提到的MUTEX其实就是互斥锁,用来解决多个线程同时操作数据库时的数据竞争问题——比如两个线程同时写数据库,可能会导致数据错乱。实现起来很简单:
第一步:先定义一个全局锁
在代码开头加一行:
import threading # 创建全局互斥锁,所有操作数据库的线程都共用这个锁 db_lock = threading.Lock()
第二步:在存储函数里用锁保护数据库操作
修改你的storeIntoSQL函数,把数据库操作的代码用锁包起来:
def storeIntoSQL(name, unknown_face_encoding): print('i am printing') # 用with语句自动获取和释放锁,避免忘记释放导致死锁 with db_lock: # 连接数据库 con = lite.connect('users2.db') with con: cur = con.cursor() # 获取新的用户ID cur.execute("SELECT DISTINCT id FROM Users ") rows = cur.fetchall() newId = len(rows)+1 # 写入数据 query = "INSERT INTO Users VALUES (?,?,?)" cur.executemany(query, [(newId, name, r,) for r in unknown_face_encoding]) # 锁会自动释放,数据库连接也会因为with语句自动关闭
这样一来,同一时间只有一个线程能执行数据库操作,就不会出现数据冲突了。
四、更省心的替代方案:线程池
如果你觉得手动管理线程麻烦,可以用concurrent.futures.ThreadPoolExecutor来管理线程池,它会自动帮你处理线程的创建和销毁:
from concurrent.futures import ThreadPoolExecutor # 定义线程池大小,比如最多同时跑5个线程 executor = ThreadPoolExecutor(max_workers=5) # 主逻辑里提交任务 with myConnection: cur = myConnection.cursor() cur.execute("Select name, image FROM images WHERE state = 1") rows = cur.fetchall() id = 0 for row in rows: print("%s, %s" % (row[0], row[1])) # 把任务提交给线程池 executor.submit(process_and_store, row[0], row[1], id) id += 1
这个方式更简洁,适合处理批量的任务。
最后再提几个小注意点:
- 尽量避免用全局变量传递
id,像上面那样直接把当前id作为参数传给线程函数,能减少多线程下的变量冲突。 - 确保
face_recognition库是线程安全的,如果它内部有全局状态,可能也需要加锁保护。 - 数据库连接最好每个线程单独创建,或者用连接池,别让多个线程复用同一个连接,容易出问题。
内容的提问来源于stack exchange,提问作者Zaid

