多线程共享全局变量的问题:
多线程就是在同个进程中运行的。因此在进程中的全局变量所有线程都可共享。这就造成了一个问题,因为线程执行过程的顺序是无序的,导致有可能造成数据错误:
这时候就需要加上一把锁,把先进到该进程上锁,即不会让别的线程进入,防止乱序,导致数据出错。特别是当数据特别大时,就容易出错。
示例代码:
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)给“消费“(爬取需求内容)掉。
生产者与消费者模式,明显表现了多线程的思想,所以用多线程的时候都可以尝试生产者与消费者模式,可以大大提高爬虫的效率。