多线程共享全局变量的问题:
多线程就是在同个进程中运行的。因此在进程中的全局变量所有线程都可共享。这就造成了一个问题,因为线程执行过程的顺序是无序的,导致有可能造成数据错误:
这时候就需要加上一把锁,把先进到该进程上锁,即不会让别的线程进入,防止乱序,导致数据出错。特别是当数据特别大时,就容易出错。
示例代码:
import threading import time VALUE = 0 gLock = threading.Lock() def add_value(): global VALUE gLock.acquire() for x in range(1000000): VALUE += 1 gLock.release() print('value=%d'%VALUE) # time.sleep(3) def main(): for x in range(2): # add_value() t = threading.Thread(target=add_value) t.start() if __name__ == '__main__': main()
其中gLock就是由threading.Lock()创建的对象,作用就是锁对象。
gLock.acquire()就是上锁操作,即不会让别的线程进入。
gLock.release()就是解锁操作,即释放锁。因为该线程操作完成了,可以放其他线程进来,这时就需要释放锁的操作。
生产者与消费者模式:
生产者与消费者模式是多线程开发中经常见到的一种模式。生产者的线程专门用来生产一些数据,如何存放到一个中间变量中。
消费者再从这个中间变量取出数据进行消费。但是因为要使用中间变量,中间变量经常是一些全局变量。
所以需要上锁机制,防止数据错乱。
示例代码:
import threading import random import time gMoney = 1000 gLock = threading.Lock() gTotalTimes = 10 gTimes = 0 class Producer(threading.Thread): def run(self): global gMoney global gTimes while True: money = random.randint(100,1000) gLock.acquire() if gTimes >= gTotalTimes: gLock.release() break gMoney += money print('%s生产了%d元钱,剩余%d元钱'%(threading.current_thread(),money,gMoney)) gTimes += 1 gLock.release() time.sleep(0.5) class Consumer(threading.Thread): def run(self): global gMoney while True: money = random.randint(100,1000) gLock.acquire() if gMoney >= money: gMoney -=money print('%s消费了%d元钱,剩余%d元钱'%(threading.current_thread(),money,gMoney)) else: if gTimes >= gTotalTimes: gLock.release() break print('%s准备消费%d元钱,剩余%d元钱,钱不足!!'%(threading.current_thread(),money,gMoney)) gLock.release() time.sleep(0.5) def main(): for x in range(3): t = Consumer(name='消费者线程%d'%x) t.start() for x in range(5): t = Producer(name='生产者线程%d'%x) t.start() if __name__ == '__main__': main()
这里采用创建类,并且继承自Thread类。
当用到全局变量就上锁,用完就释放锁。即用到全局变量都会有 gLock.acquire() ...... gLock.release() 操作。
threading.current_thread()方法可以得出该线程的名称。
总之,生产者与消费者模式是一种爬虫思想。示例化就是,生产者可以负责“生产”(爬取)目标的具体 url 列表,消费者是把存放在生产者的“产品”(具体url)给“消费“(爬取需求内容)掉。
生产者与消费者模式,明显表现了多线程的思想,所以用多线程的时候都可以尝试生产者与消费者模式,可以大大提高爬虫的效率。