爬虫学习使用指南

Auth: 王海飞

Data:2018-06-05

Email:779598160@qq.com

github:https://github.com/coco369/knowledge

前言

使用线程时最不愿意遇到的情况就是多个线程竞争资源,在这种情况下为了保证资源状态的正确性,我们可能需要对资源进行加锁保护的处理,这一方面会导致程序失去并发性,另外如果多个线程竞争多个资源时,还有可能因为加锁方式的不当导致死锁。

要实现将资源和持有资源的线程进行绑定的操作,最简单的做法就是使用threading模块的local类,在网络爬虫开发中,就可以使用local类为每个线程绑定一个MySQL数据库连接或Redis客户端对象,这样通过线程可以直接获得这些资源,既解决了资源竞争的问题,又避免了在函数和方法调用时传递这些资源。

1. 锁的概念

线程锁:其实并不是给资源加锁, 而是用锁去锁定资源,你可以定义多个锁,当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理

基本语法:

#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([timeout])
#释放
mutex.release()

代码:怎么使用锁去锁住资源,释放锁

import threading
import time

counter = 0
# 只是定义一个锁,并不是给资源加锁,你可以定义多个锁,像下两行代码,当你需要占用这个资源时,任何一个锁都可以锁这个资源
counter_lock = threading.Lock()
counter_lock2 = threading.Lock()
counter_lock3 = threading.Lock()


# 可以使用上边三个锁的任何一个来锁定资源

class MyThread(threading.Thread):
    # 使用类定义thread,继承threading.Thread
    def __init__(self, name):
        super(MyThread, self).__init__()
        self.name = "Thread-" + str(name)

    def run(self):  # run函数必须实现
        # 多线程是共享资源的,使用全局变量
        global counter, counter_lock
        # 当需要独占counter资源时,必须先锁定,这个锁可以是任意的一个锁,可以使用上边定义的3个锁中的任意一个
        time.sleep(1);
        if counter_lock.acquire():
            counter += 1
            print("I am %s, set counter:%s" % (self.name, counter))
            # 使用完counter资源必须要将这个锁打开,让其他线程使用
            counter_lock.release()


if __name__ == "__main__":
    for i in range(1, 101):
        my_thread = MyThread(i)
        my_thread.start()

以上代码,只是教会你怎么去使用锁,锁住资源,当一个线程在使用锁住的资源的时候,其他线程则无法再使用该资源了,起到了很好的避免资源的竞争。

2. 多线程去打印输出自增的全局变量

定义一个简单的多线程,主要功能是用于打印全局递增的变量参数,并打印线程名。

可以不妨想想,如果启动多线程对同一全局变量进行递增操作的话,就有可能多个线程同时获取到全局变量,然后进行递增操作。可想而知,如果是这样的话,那么打印出来的变量就有可能会重复。这就是出现了一个进程中,多线程同时共享一个资源的时候,出现的资源竞争的问题了。我们先查看一下代码的运行结果,然后进行分析:

import threading
import time


class MyThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global n, lock

		# 休眠1秒钟
        time.sleep(1)
		
		# 线程主要打印循环地址的n值,和对于的线程的名称,线程名也可以使用self.name来获取
        print(n, threading.current_thread().name)
        n += 1


if __name__ == '__main__':
	
	# 设置全局变量
    n = 1
    ThreadList = []
	# 创建线程锁
    lock = threading.Lock()

	# 创建20个线程
    for i in range(1, 20):
        t = MyThread()
        ThreadList.append(t)
	
	# 启动线程
    for t in ThreadList:
        t.start()
	
	# 阻塞线程
    for t in ThreadList:
        t.join()

运行结果:

1 Thread-2
1 Thread-3
1 Thread-1
4 Thread-7
4 Thread-5
4 Thread-9
4 Thread-4
5 Thread-6
6 Thread-11
7 Thread-10
9 Thread-8
12 Thread-15
12 Thread-19
12 Thread-18
12 Thread-16
12 Thread-17
14 Thread-12
18 Thread-14
19 Thread-13

Process finished with exit code 0

分析:

很明显的在我们的执行结果中,打印的全局变量n中有很多重复的,这就印证了我们的多线程在共享资源上存在不可避免的资源竞争的关系。为了解决这种资源的竞争,我们可以采取线程锁的形式去避免对资源的竞争

3. 优化多线程打印输出自增的全局变量

代码优化:

def run(self):
    global n, lock
    time.sleep(1)

	# 判断是否锁定了资源
    if lock.acquire():
		
		# 如果锁定了资源,则打印如下的全局变量n和线程名,并且全局变量n自增1
        print(n, self.name)
        n += 1

		# 释放锁
        lock.release()

运行结果:

1 Thread-2
2 Thread-3
3 Thread-1
4 Thread-4
5 Thread-5
6 Thread-8
7 Thread-9
8 Thread-12
9 Thread-13
10 Thread-6
11 Thread-16
12 Thread-17
13 Thread-18
14 Thread-7
15 Thread-19
16 Thread-11
17 Thread-10
18 Thread-15
19 Thread-14

Process finished with exit code 0

分析:在优化的代码中,我们先建立了一个threading.Lock类对象lock,在run方法里,我们使用lock.acquire()获得了这个锁。此时,其他的线程就无法再获得该锁了,他们就会阻塞在“if lock.acquire()”这里,直到锁被另一个线程释放:lock.release()。所以,if语句中的内容就是一块完整的代码,不会再存在执行了一半就暂停去执行别的线程的情况。所以最后结果是整齐的。完美的解决了多线程竞争同一资源造成的问题了。


书籍推荐