刚开始研究多线程,看了很多视频跟文章,但还是概念混淆,所以过来论坛求助。
1、
对于爬虫而言,for 循环多线程抓取 N 个页面,然后获取内容到 MongoDB 数据库,错误链接写入 error.txt,是线程安全的吗?如果不安全,我通过在类里面的采集入库跟写入文件加锁,是不是可以解决?我之前没加锁,抓了 36W 条数据,感觉没发现有什么问题。
入库部分如下:
# 启动抓取函数
def run(self):
try:
rst_json = self.claw_detail()
if rst_json != None:
gLock.acquire()
result = table_lines.insert_one(rst_json)
gLock.release()
else:
result = 'null'
except:
with open(file_path + "/error.txt", 'a+') as f1:
f1.write(self.hotelid)
f1.write('\n')
f1.flush()
result = 'error'
time.sleep(5)
finally:
time.sleep(1)
return result
2、
多层 for 循环嵌套的时候,这么用多线程可以吗? t.join()这么用没有问题吧? t.setDaemon(True)需要设置吗?
def main(): for dateTuple in dateList:
threads = []
for i in id_lines.find():
hotelId = i.get('hotelId')
threads.append(hotelId)
for hotelid in threads:
t = ClawData(hotelid,headersCookie)
#t.setDaemon(True)
t.start()
for hotelid in threads:
t.join()
time.sleep(3)
非常感谢!
1
seoguess OP 如果 for 循环加上加锁不安全的话,那么 Queue 队列应该如何应用?
非生产者消费者模式感觉太复杂了,抓取的类只需要传入 cookie 跟对应的 id 来生成 url。 或者我把 url 跟 cookie 变成一个 tuple,然后判断 not Queue.empty(),然后通过 for 循环 + Queue.get()多线程去抓取内容可行? 求解惑,谢谢! |
2
Juszoe 2019-04-25 19:02:17 +08:00 1
python 有全局锁 GIL,不用考虑线程不安全的问题,IO 型任务多线程随便用,除非你要把一段代码做成原子操作
|
3
scriptB0y 2019-04-25 19:07:35 +08:00 1
@Juszoe 错啦,Python 的 GIL 带来的作用是虚拟机的 opcode 是线程安全的,但是并不是每一行代码是线程安全的。请看这个例子:
https://python-parallel-programmning-cookbook.readthedocs.io/zh_CN/latest/chapter2/06_Thread_synchronization_with_Lock_and_Rlock.html |
4
scriptB0y 2019-04-25 19:11:31 +08:00
对于楼主的问题,不是线程安全的,会有多个线程同时操作数据库,但是只要你的任务之间不相关,没有共享资源,即使线程不安全也是没有问题滴。
但是有几点建议楼主可以参考: 1. 写文件肯定是非线程安全的,你这种用法应该使用 logging 模块,第一他是 thread safe 的,第二它是专门写 log 的。 2. 你不需要自己操作这么低级的 API 啦,可以看下 concurrent 模块,里面封装了线程池,用起来更方便! https://docs.python.org/3/library/concurrent.futures.html |
6
scriptB0y 2019-04-25 19:19:42 +08:00
@Juszoe 是的,即使是相关的,也是需要一定的几率才会发现线程不安全的现象。Python3 GIL 调度策略换了,出现的几率更低了。
|
8
Leigg 2019-04-25 19:45:03 +08:00 via iPhone 1
没有同步或者资源竞争问题不讨论线程安全,setDaemon 需要设置 true,否则当程序异常停止时,会有僵尸线程。
|
9
seoguess OP @Leigg 谢谢!如果我的 main 函数大致结构是:
for cookie in cookies: # 1、获取 N 个不同的生成的 cookie for id in id_list: #2、获取不同的 id 来生成 url t = threading.Thread(claw(cookie,id), args) # 3、多线程获取内容、入库、记录错误 t.start() 是不是逻辑上,我把第三步中的采集入库、记录错误上锁了,1、2 中对应的 cookie 跟 id 不会出现多线程引起的数据错乱? |
10
Leigg 2019-04-25 21:35:21 +08:00 via iPhone
记录错误用 logging,不能用 with open ;
入库记得 db 连接用连接池,或者入库单独一个函数,总之不要每个线程创建一个 db 连接,没有其他的同步或者资源竞争问题吧? |
11
seoguess OP @scriptB0y 我用 concurrent 模块重新修改了下代码,发现效率比我之前的代码差了好多....
for 循环: #获取 cookie: threads = [ (i.get('hotelId'),headersCookie) for i in id_lines.find() ] pool = ThreadPoolExecutor() future_tasks = [ pool.submit(start_claw, t) for t in threads ] wait(future_tasks, return_when=ALL_COMPLETED) time.sleep(3) 3K 左右的链接,用时 382 秒 for 循环: #获取 cookie: threads = [] for i in id_lines.find(): hotelId = i.get('hotelId') threads.append(hotelId) for hotelid in threads: t = ClawData(hotelid,headersCookie) t.setDaemon(True) #防止程序异常退出时,有僵尸进程存在 t.start() for hotelid in threads: t.join() time.sleep(3) 用时:52 秒 请问为啥效率可以差别这么大? |
12
zy342500 2019-04-26 17:43:58 +08:00 1
@seoguess pool = ThreadPoolExecutor(max_workers=20) 这样写 给他设置工作线程数
|
13
seoguess OP |
14
seoguess OP 原来 max_worker 为空的情况下,默认线程为 cpu 核数量*5,难怪花了 300+秒。
|