由于单进程爬虫的种种弊端,以及大量获取数据的需要,我最近开始写分布式爬虫。尽管网上已经有比较现成的方案,如scrapy+rq等,但是出于种种原因考虑,比如部署的难易程度,任务比较单一,以及想自己练练手等,还是决定由自己实现尽可能多的功能。
在写的过程中,不可避免的需要以多线程甚至多进程运行程序。因此解决多线程间以及多进程间的同步和通信问题成为必须。由于进程拥有独立资源,因此多进程同步比多线程同步要更难一些。再加上虽然python中的GIL锁限制了多线程运行的效率,但是由于相关任务比较简单,并不需要太多计算量,因此决定先使用多线程编程。
为了解决这个问题,我先查了查网上的资料,发现似乎所有的解决方案都是基于单个文件的。什么叫基于单个文件呢?就是线程间需要共享的数据都是声明在主文件中的,或者main函数中的
a=1 #需要共享的变量
lock=threading.Lock() #锁
class test():
def __init__(self):
b=2
if __name__=='__main__':
c=3
举例来说,上面这个文件,经过测试发现a是全局变量。而c似乎也是全局变量,但是我有点不太确定。而b则肯定不是全局变量。网上普遍的解决方案是,在a的位置声明共享变量和lock,这样无论在该文件中什么位置更改变量a,都会造成所有位置中a的值改变。再配合lock,能够解决生产者和消费者问题。但是我发现,当应用场景更复杂一些时,比如需要将这些功能封装在一个类中并且由其他进程来调用这个类时,上述方法不再有效。因为封装类中的变量不再是全局变量
我想了想,目前我能想到的解决方法主要有两个,一个是将变量a声明为全局变量,在子线程中再声明一下global a,这样在该线程的子线程中,任何一个子线程改动a,所有a值都会改变。
__author__ = 'multiangle'
import time
import threading
class a():
def __init__(self):
global data
data=5
thread=b()
thread.start()
self.loop()
def loop(self):
while True:
time.sleep(0.2)
print(data)
class b(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
time.sleep(0.4)
global data
data+=1
if __name__=='__main__':
a()
如上所示,能够得到输出结果为
5
6
6
7
7
8
8
9
9
10
. . .
另外一个方法是将要共享的变量以指针的形式传递给各个子线程,这样所有a都指向同一块内存,自然能够实现变量的同步。由于类对象实例本质也是指针,因此也可以将要共享的变量封装成类在子线程间传递,既实现了内容同步,也保护了要共享的变量。这样有一个潜在的隐患就是所有线程的IO压力都会集中这个类的实例上,可能会造成性能瓶颈,不过目前也不考虑这么多啦。下面上代码
下面这段代码涉及了python的多线程编程,变量共享,还涉及了一些多进程编程。
这段代码总共4个类,
boss,顾名思义,总揽全局,跟踪需要共享变量data的情况,
consum,消费进程
product,生产进程
list_manage , 变量类,核心是要共享的变量pool,还增加了一些外围方法。
lock和data以指针的形式在子线程中传递,观察输出结果可以发现data是在子线程间同步变化的
import threading
import time
from multiprocessing import Process
class boss():
def __init__(self):
data=list_manage()
lock=threading.Lock()
self.data=data
c0=consume(data,'c0',lock)
p=product(data,'p',lock)
c1=consume(data,'c1',lock)
c0.start()
p.start()
c1.start()
self.go()
def go(self):
while True:
time.sleep(0.1)
# print('root process',self.data)
# self.data.print()
print('boss check , ',self.data.show())
class consume(threading.Thread):
def __init__(self,data,id,lock):
threading.Thread.__init__(self)
self.id=id
self.data=data
self.lock=lock
# self.run(data)
def run(self):
while True :
time.sleep(1)
self.lock.acquire()
print('thread ',self.id,' consume once,get ',self.data.get())
self.lock.release()
class product(threading.Thread):
def __init__(self,data,id,lock):
threading.Thread.__init__(self)
self.id=id
self.data=data
self.lock=lock
def run(self):
count=0
while True:
count+=1
time.sleep(0.4)
self.lock.acquire()
self.data.add(count)
self.lock.release()
print('thread ',self.id,' product once, add ',count)
class list_manage():
def __init__(self):
self.pool=[1,2,3]
def get(self):
if self.pool.__len__()>0:
return self.pool.pop()
else:
return None
def add(self,data):
self.pool.append(data)
def print(self):
print(self.pool)
def show(self):
copy=self.pool[:]
return copy
if __name__=='__main__':
p=Process(target=boss,args=())
p.start()
以下为输出结果,只截取了最开始的一段
boss check , [1, 2, 3]
boss check , [1, 2, 3]
boss check , [1, 2, 3]
thread p product once, add 1
boss check , [1, 2, 3, 1]
boss check , [1, 2, 3, 1]
boss check , [1, 2, 3, 1]
boss check , [1, 2, 3, 1]
thread p product once, add 2
boss check , [1, 2, 3, 1, 2]
boss check , [1, 2, 3, 1, 2]
thread c0 consume once,get 2
thread c1 consume once,get 1
boss check , [1, 2, 3]
boss check , [1, 2, 3]
thread p product once, add 3
boss check , [1, 2, 3, 3]
boss check , [1, 2, 3, 3]
boss check , [1, 2, 3, 3]
boss check , [1, 2, 3, 3]
thread p product once, add 4
boss check , [1, 2, 3, 3, 4]
boss check , [1, 2, 3, 3, 4]
boss check , [1, 2, 3, 3, 4]
boss check , [1, 2, 3, 3, 4]
thread c0 consume once,get 4
thread c1 consume once,get 3
thread p product once, add 5
boss check , [1, 2, 3, 5]
boss check , [1, 2, 3, 5]
boss check , [1, 2, 3, 5]
boss check , [1, 2, 3, 5]
thread p product once, add 6
boss check , [1, 2, 3, 5, 6]
boss check , [1, 2, 3, 5, 6]
boss check , [1, 2, 3, 5, 6]