【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

在锁机制这里还要提醒的是,加锁和释放锁都是需要消耗内存空间的,因此不要频繁使用锁,仅在涉及修改和写操作的时候用,而访问读取等操作则不必要使用。

 


posted @ 2018-09-25 22:08  ChanKaion  阅读(370)  评论(0编辑  收藏  举报