具有线程安全的python线程队列生成器使用者
我正在使用线程和队列获取url并存储到数据库。具有线程安全的python线程队列生成器使用者,python,multithreading,queue,Python,Multithreading,Queue,我正在使用线程和队列获取url并存储到数据库。 我只需要一个线程来完成存储工作。 所以我写代码如下: import threading import time import Queue site_count = 10 fetch_thread_count = 2 site_queue = Queue.Queue() proxy_array=[] class FetchThread(threading.Thread): def __init__(self,sit
我只需要一个线程来完成存储工作。
所以我写代码如下:
import threading
import time
import Queue
site_count = 10
fetch_thread_count = 2
site_queue = Queue.Queue()
proxy_array=[]
class FetchThread(threading.Thread):
def __init__(self,site_queue,proxy_array):
threading.Thread.__init__(self)
self.site_queue = site_queue
self.proxy_array = proxy_array
def run(self):
while True:
index = self.site_queue.get()
self.get_proxy_one_website(index)
self.site_queue.task_done()
def get_proxy_one_website(self,index):
print '{0} fetched site :{1}\n'.format(self.name,index)
self.proxy_array.append(index)
def save():
while True:
if site_queue.qsize() > 0:
if len(proxy_array) > 10:
print 'save :{0} to database\n'.format(proxy_array.pop())
else:
time.sleep(1)
elif len(proxy_array) > 0:
print 'save :{0} to database\n'.format(proxy_array.pop())
elif len(proxy_array) == 0:
print 'break'
break
else:
print 'continue'
continue
def start_crawl():
global site_count,fetch_thread_count,site_queue,proxy_array
print 'init'
for i in range(fetch_thread_count):
ft = FetchThread(site_queue,proxy_array)
ft.setDaemon(True)
ft.start()
print 'put site_queue'
for i in range(site_count):
site_queue.put(i)
save()
print 'start site_queue join'
site_queue.join()
print 'finish'
start_crawl()
执行输出:
init
put site_queue
Thread-1 fetched site :0
Thread-2 fetched site :1
Thread-1 fetched site :2
Thread-2 fetched site :3
Thread-1 fetched site :4
Thread-2 fetched site :5
Thread-1 fetched site :6
Thread-2 fetched site :7
Thread-1 fetched site :8
Thread-2 fetched site :9
save :9 to database
save :8 to database
save :7 to database
save :6 to database
save :5 to database
save :4 to database
save :3 to database
save :2 to database
save :1 to database
save :0 to database
break
start site_queue join
finish
[Finished in 1.2s]
为什么save()
函数在site\u queue.join()之后运行
我还用线程函数替换了save()
,但它不起作用。
这是否意味着我必须将proxy\u array=[]
更改为proxy\u queue=queue.queue()
我只想让一个THAD来做这件事,而且没有任何其他THAD可以从proxy\u array
获取数据,我为什么要加入它?使用队列似乎很奇怪。
有更好的解决方案吗
更新:
我不想等到所有的FetchThreads都完成它们的工作。我想在Fetching的同时保存数据,这样会快得多。
我希望结果如下所示(因为我使用array.pop(),所以保存0可能会很晚出现,这只是一个易于理解的示例。):
某人的更新2有如下相同的问题:
问题:
正如我在上面的上下文中所说,没有任何其他TheAD可以从proxy_数组获取数据。
我简直无法想象它为什么会断开线程安全性?
回答:
在米莎的回答中,我仔细阅读后明白了
问题:
还有一个问题是,程序主线程是否可以与FetchThreads一起使用(换句话说,不需要创建StoreThread)
这是我无法理解的,我会在找到答案后进行更新。我建议您阅读有关的内容。您的生产者是获取线程。您的消费者是保存
功能的消费者。如果我理解正确,您希望使用者尽快保存获取的结果。为了实现这一点,生产者和消费者必须能够以某种线程安全的方式(例如队列)进行通信
基本上,您需要另一个队列。它将取代代理数组。您的保存
功能将如下所示:
while True:
try:
data = fetch_data_from_output_queue()
save_to_database(data)
except EmptyQueue:
if not stop_flag.is_set():
# All done
break
time.sleep(1)
continue
input_queue = initialize_input_queue()
ouput_queue = initialize_output_queue()
stop_flag = Event()
create_and_start_save_thread(output_queue) # read from output queue, save to DB
create_and_start_fetch_threads(input_queue, output_queue) # get sites to crawl from input queue, push crawled results to output_queue
join_fetch_threads() # this will block until the fetch threads have gone through everything in the input_queue
stop_flag.set() # this will inform the save thread that we are done
join_save_thread() # wait for all the saving to complete
此save
函数需要在自己的线程中运行stop_标志
是在加入提取线程后设置的
从较高的层次来看,您的应用程序如下所示:
while True:
try:
data = fetch_data_from_output_queue()
save_to_database(data)
except EmptyQueue:
if not stop_flag.is_set():
# All done
break
time.sleep(1)
continue
input_queue = initialize_input_queue()
ouput_queue = initialize_output_queue()
stop_flag = Event()
create_and_start_save_thread(output_queue) # read from output queue, save to DB
create_and_start_fetch_threads(input_queue, output_queue) # get sites to crawl from input queue, push crawled results to output_queue
join_fetch_threads() # this will block until the fetch threads have gone through everything in the input_queue
stop_flag.set() # this will inform the save thread that we are done
join_save_thread() # wait for all the saving to complete
我必须想出一些类似的生产者消费者。生产者生成一个“id”,消费者使用该id进行url获取并处理。
这是我的框架代码,它可以做到这一点
import Queue
import random
import threading
import time
import sys
data_queue = Queue.Queue()
lock = threading.Lock()
def gcd(a, b):
while b != 0:
a,b = b, a%b
return b
def consumer(idnum):
while True:
try:
data = data_queue.get(block=False)
except Exception, e:
print 'Exception ' + str(e)
else:
with lock:
print('\t consumer %d: computed gcd(%d, %d) = %d' %(idnum, data[0], data[1], gcd(data[0], data[1])))
time.sleep(1)
data_queue.task_done()
def producer(idnum, count):
for i in range(count):
a,b = random.randint(1, sys.maxint), random.randint(1, sys.maxint)
with lock:
print('\t producer %d: generated (%d, %d)'% (idnum, a, b))
data_queue.put((a,b))
time.sleep(0.5)
if __name__ == '__main__':
num_producers = 1
num_consumers = 2
num_integer_pairs = 10
for i in range(num_consumers):
t = threading.Thread(target=consumer, args=(i,))
t.daemon = True
t.start()
threads = []
for ii in range(num_producers):
thread = threading.Thread(target=producer, args=(ii, num_integer_pairs))
threads.append(thread)
thread.start()
# wait for the producers threads to finish
for thread in threads:
thread.join()
print 'done with producer threads'
# wait till all the jobs are done in the queue
data_queue.join()
with lock:
print 'all consumer threads finished'
with lock:
print 'main thread exited'
如果查看输出,“开始站点队列连接”出现在保存行之后,因此保存函数在site\u queue.join()之前运行,不是在之后。@misha但是为什么它在所有FetchThread
完成之后才开始保存数据呢?因为没有什么可以阻止它这么做。@misha我认为save
应该在len(proxy\u array)>0
时执行。即使我将site\u count
更改为100,结果是一样的。@misha我已经更新了问题。我不想等到所有的FetchThreads都完成了这项工作。我想在Fetching时保存数据,速度会快得多。好的,在看到你更新的问题后,我现在明白了。我已经更新了我的答案。谢谢你仔细的回答!所以我必须创建一个StoreThread(threading.Thread)
。但是我无法想象为什么它会破坏线程安全
,没有任何其他的线程可以从代理数组中获取数据。还有一个问题,如果程序主线程可以与FetchThreads一起使用(换句话说,不需要创建StoreThread)?你需要一个额外的线程,因为你正在独立地做两件事:1)等待工作人员加入,这样你就可以停止flag.set()
;2)保存数据。由于join
是一个阻塞调用,因此需要在单独的线程上执行1)和2)。您可以通过使用非阻塞联接来避免这种情况,但这样做只会付出更多的努力而没有什么好处。data\u queue.task\u done()
应该位于try except else中的else