【Python爬虫学习笔记9】threading多线程
多线程简介
多线程,即允许程序多个线程并发地执行。多线程是为了同步完成多项任务,借助提高资源使用效率来提高系统的效率。最简单的比喻多线程就像火车的每一节车厢,而进程则是火车。车厢离开火车是无法跑动的,同理火车也不可能只有一节车厢。多线程的出现就是为了提高效率。[源自百度百科:多线程]
threading 模块
在python中有专门用于提供多线程编程的模块——threading,其中最常用的类就是Thread类,通常初始化时只需要传入用于线程执行的目标函数名(要注意是函数名,而不是函数,否则就会传入原函数的返回值)。示例如下:
## 简单多线程使用示例 import threading import time # 定义待执行的多线程函数 def eating(): for num in range(3): print('%s is eating...%s',(threading.current_thread(),num)) time.sleep(1) def drinking(): for num in range(3): print('%s is drinking...%s',(threading.current_thread(),num)) time.sleep(1) # 多线程主函数——实例化两个线程并调用start()方法运行 def thread_main(): t1 = threading.Thread(target=eating) t2 = threading.Thread(target=drinking) t1.start() t2.start() if __name__ == '__main__': thread_main() """ Output: %s is eating...%s (<Thread(Thread-1, started 4420)>, 0) %s is drinking...%s (<Thread(Thread-2, started 8968)>, 0) %s is eating...%s (<Thread(Thread-1, started 4420)>, 1) %s is drinking...%s (<Thread(Thread-2, started 8968)>, 1) %s is eating...%s (<Thread(Thread-1, started 4420)>, 2) %s is drinking...%s (<Thread(Thread-2, started 8968)>, 2) """
从运行结果中可以看到这两个线程交替地执行,实际上这是一个并发(速度由计算机而定)的过程,这相比于顺序执行的速度会快很多。这里还要说明的地方是,我们可以使用threading.current_thread()方法来获取当前执行的线程名,也还可以用threading.enumerate()来获取当前程序存在的线程数。
另外我们也可以基于类的编程把其封装成一个个的线程类,继承threading.Thread类并重写其中的run()方法。比如上例的类改写如下:
## 基于类的多线程编程示例 import threading import time # 定义线程类继承Thread类并重写其中的run()方法 class EatingThread(threading.Thread): def run(self): for num in range(3): print('%s is eating...%s',(threading.current_thread(),num)) time.sleep(1) class DrinkingThread(threading.Thread): def run(self): for num in range(3): print('%s is drinking...%s',(threading.current_thread(),num)) time.sleep(1) def thread_main(): t1 = EatingThread() t2 = DrinkingThread() t1.start() t2.start() if __name__ == '__main__': thread_main()
锁机制
在了解锁机制前,我们先来看一下下面这个例子:
## 使用多线程进行加法运算 import threading # 定义全局变量VALUE VALUE = 0 # 定义加法线程函数 def add_value(): global VALUE for x in range(1000000): VALUE += 1 print('value = ', VALUE) # 定义两个线程并发执行加法操作 def add_thread_main(): for x in range(2): t = threading.Thread(target=add_value) t.start() if __name__ == '__main__': add_thread_main() """ Output: value = 1147074 value = 1211397 """
上面的示例按照我们的逻辑看来应该是依次输出1000000和2000000,但结果并不是这样的,这就是常说的多线程共享全局变量问题。其实在我们执行线程时,执行的顺序是不一定的,也就是说有时候可能重合在一起执行,因而导致有时虽二者都对共享变量进行了一次加法(即本应加两次)而实际上只真正加了一次。
而为了解决这样的问题,threading模块提供了一个Lock类,这个类可以在某个线程访问某个变量的时候加锁,其他线程此时不能访问该变量,直到加锁线程处理完控制变量并把锁释放了,其他线程才能进行访问处理。锁机制使用起来也很简单,由于是多个线程访问共享变量,因而需设置一个全局的Lock类对象,然后在访问前后分别使用Lock类的acquire()方法加锁和release()方法释放锁。
上述例子使用锁机制仅需做以下几处的修改:
1.定义全局变量:
gLock = threading.Lock()
2.在修改共享变量前后加锁和释放锁
gLock.acquire()
for x in range(1000000):
VALUE += 1
gLock.release()
如此一来,输出结果便是:
value = 1000000
value = 2000000
在锁机制这里还要提醒的是,加锁和释放锁都是需要消耗内存空间的,因此不要频繁使用锁,仅在涉及修改和写操作的时候用,而访问读取等操作则不必要使用。