Python ThreadPoolExecutor多线程并发写同文件内容错乱问题
问题根因
多线程并发场景下,文件写入操作不是原子操作,多个线程同时抢占文件IO资源执行写入时,不同线程的写入内容会互相打断,最终出现内容交错混杂的问题。除此之外,代码中count_requests、host_error两个全局计数变量的修改也没有做线程安全保护,高并发下会出现计数不准的问题,且原有写入逻辑未拼接原始URL、未加换行符,本身也不符合预期输出格式要求。
可行解决方案
- 方案1:加线程锁
引入threading.Lock线程同步锁,所有线程在执行文件写入、修改全局计数操作前必须先获取锁,操作完成后释放锁,保证同一时间点只有一个线程执行上述敏感操作,避免竞争。该方案实现简单,但写入逻辑会退化为串行,高并发下锁等待会带来一定性能开销。 - 方案2:主线程统一写入(推荐)
子线程仅负责发起HTTP请求、解析页面标题,不直接操作文件、不修改全局变量,将解析结果通过返回值传回主线程;主线程统一收集所有子线程的执行结果,集中完成计数、文件写入、进度打印操作。该方案完全规避多线程资源竞争问题,不需要加锁,逻辑更清晰可维护。
推荐方案参考实现代码
import requests from concurrent.futures import ThreadPoolExecutor, as_completed import fileinput from bs4 import BeautifulSoup from requests.packages.urllib3.exceptions import InsecureRequestWarning import warnings requests.packages.urllib3.disable_warnings(InsecureRequestWarning) warnings.filterwarnings("ignore", category=UserWarning, module='bs4') def get_url(url): url = url.strip() try: result_request = requests.get(url, verify=False, timeout=10) soup = BeautifulSoup(result_request.text, 'html.parser') title = soup.title.get_text(strip=True) return (True, url, title) except Exception as e: return (False, url, str(e)) if __name__ == "__main__": count_requests = 0 host_error = 0 # 提前清空输出文件 with open('outfile.txt', 'w', encoding="utf-8") as f: pass # 创建线程池 with ThreadPoolExecutor(max_workers=100) as pool: all_task = [] # 提交所有任务 for line in fileinput.input(['urls.txt']): if line.strip(): all_task.append(pool.submit(get_url, line)) # 逐次收集已完成的任务结果,统一写入 for future in as_completed(all_task): is_success, url, content = future.result() if is_success: count_requests += 1 with open('outfile.txt', 'a', encoding="utf-8") as f: f.write(f"{url} - {content}\n") else: host_error += 1 # 实时打印真实进度 print(f"requests success : {count_requests} | requests error {host_error}", end='\r')
补充说明
如果坚持使用子线程写入的方案,只需要在全局初始化锁对象write_lock = threading.Lock(),将文件写入、计数修改的逻辑放到with write_lock:上下文块中即可,注意锁的范围不要覆盖HTTP请求、页面解析等耗时逻辑,避免不必要的性能损耗。
内容的提问来源于stack exchange,提问作者yand
相关产品推荐
相关产品推荐

