多线程共享全局变量的问题:

多线程就是在同个进程中运行的。因此在进程中的全局变量所有线程都可共享。这就造成了一个问题,因为线程执行过程的顺序是无序的,导致有可能造成数据错误:

这时候就需要加上一把锁,把先进到该进程上锁,即不会让别的线程进入,防止乱序,导致数据出错。特别是当数据特别大时,就容易出错。

示例代码:



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)给“消费“(爬取需求内容)掉。

生产者与消费者模式,明显表现了多线程的思想,所以用多线程的时候都可以尝试生产者与消费者模式,可以大大提高爬虫的效率。