Python爬虫——多线程爬虫如何实现?

Python爬虫——多线程爬虫

  • 1.多任务
  • 2.主线程与子线程
    • 2.1 何谓线程、主线程及子线程
    • 2.2 查看线程数量
    • 2.3 创建子线程
    • 2.4 线程间的通信
  • 3.线程间的资源竞争
  • 4.互斥锁与死锁
    • 4.1 互斥锁
    • 4.2 死锁
    • 4.3 避免死锁
  • 5.Queue线程
  • 6.线程同步的实现

1.多任务

多任务指的是在同一时间不同任务需要同时进行的场景,比如边听歌边刷题,边看电视边吃饭…

  • 要实现多任务的进行,我们首先会想到的方式如下:
import time

# 吃饭
def Eat():
    for i in range(4):
        print('eating...')
        time.sleep(1)
# 看电视
def Watch():
    for i in range(4):
        print('watching...')
        time.sleep(1)

if __name__ == '__main__':
    Eat()
    Watch()
    
# --结果:--
# eating...
# eating...
# eating...
# eating...
# watching...
# watching...
# watching...
# watching...

嗯,,,但这是同时进行吗?仔细一想他们是有先后顺序的吧。

2.主线程与子线程

2.1 何谓线程、主线程及子线程

  • 线程:每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。
  • 主线程:程序启动时自己执行本身的代码的线程
    子线程:用户自己创建的线程
  • 主线程和子线程之间的关系:主线程是在子线程结束之后再执行的
  • 实现方式:join()方法,使子线程结束后再执行主线程;setDaemon()守护线程,不会等待子线程,有空就执行
    看看代码和结果消化一下吧!
import threading
import time

def speak():
    # 子线程
    print('A先说!')
    time.sleep(1)

if __name__ == '__main__':
    for i in range(5):
        t = threading.Thread(target=speak)
        t.start()
    print('B有话要说!')

结果(执行了几十次吧)出现了以下三种情况:
Python爬虫——多线程爬虫如何实现?_第1张图片
Python爬虫——多线程爬虫如何实现?_第2张图片
Python爬虫——多线程爬虫如何实现?_第3张图片
在t.start()后面添加

t.join()

时间会按照先后顺序来了,但是结果确实只有第三种了,剩下的setDaemon()加了与没加效果一样

2.2 查看线程数量

有事我们需要查看有哪些进程运行了可以使用:threading.enumerate()

例如:

import threading
import time

def speak():
    # 子线程
    print('A先说!')
    time.sleep(1)

if __name__ == '__main__':
    for i in range(5):
        t = threading.Thread(target=speak)
        t.setDaemon(True)
        t.start()
    print('B有话要说!')
    print(threading.enumerate())
    print('共有'+str(len(threading.enumerate()))+'个线程...')
    
# ---结果:---
# A先说!
# # A先说!
# # A先说!
# # A先说!
# # A先说!B有话要说!
# # [<_MainThread(MainThread, started 10900)>, , , , , ]
# # 
# # 共有6个线程...

2.3 创建子线程

当然,为了简化子线程的创建,我们通过类的继承来实现,而我们所需要实现的内容,都在run方法之中进行重写。

import threading
import time

class MyThread(threading.Thread):
    # 继承threading.Thread类创建子线程
    def __init__(self,name):
        super().__init__(name=name)
    def run(self):
        for i in range(3):
            print('我是{}{}'.format(self.name,i))

if __name__ == '__main__':
    thread = MyThread('子线程')
    thread.start()
    
# --结果:--
# 我是子线程0
# 我是子线程1
# 我是子线程2

2.4 线程间的通信

  • 在⼀个函数中,对全局变量进⾏修改的时候,是否要加global要看是否对全局变量的指向进⾏了修改,如果修改了指向,那么必须使⽤global,仅仅是修改了指 向的空间中的数据,此时不⽤必须使⽤global
  • 线程是共享全局变量
  • 多线程参数的使用:threading.Thread(target=test, args=(num,))

3.线程间的资源竞争

  • 一个线程写入以及读取不会对资源的分配产生影响,但是当多个线程进行写入和读取时,他们之间就会产生资源竞争
  • 具体的资源竞争我们可以通过一个实例来进进行查看
import threading
import time

num = 0
def Thread1(nums):
    global num
    for i in range(nums):
        num += 1
    print('Thread1---{}'.format(num))

def Thread2(nums):
    global num
    for i in range(nums):
        num += 1
    print('Thread1---{}'.format(num))

if __name__ == '__main__':
    t1 = threading.Thread(target=Thread1, args=(1000000,))
    t2 = threading.Thread(target=Thread2, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(2)
    print('main---{}'.format(num))
    
# --结果:--
# Thread1---1074807
# Thread1---1400032
# main---1400032

以上结果显示,输出的结果不为我们所认为正确的答案,其原因就是资源竞争,那么何谓资源竞争?

  • 当系统中供多个进程所共享的资源,不足以同时满足它们的需要时,引起它们对资源的竞争而产生死锁。

4.互斥锁与死锁

4.1 互斥锁

  • 当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制
    某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能改变,直到该线程释放资源,将资源的状态变成"⾮锁定",其他的线程才能再次锁定该资源。互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性
import threading
# 创建锁
mutex = threading.Lock()
# 锁定锁
mutex.acquire()
# 解锁
mutex.release()

4.2 死锁

  • 在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时等待对⽅的资源,就会造成死锁
    上个代码仔细再体会一下:
import threading
import time

# 创建锁A、B
mutexA = threading.Lock()
mutexB = threading.Lock()

class thread1(threading.Thread):
    def run(self):
        mutexA.acquire()
        print('thread1--A上锁成功')
        time.sleep(1)
        mutexB.acquire()
        print('thread1--B上锁成功')
        mutexA.release()
        mutexB.release()

class thread2(threading.Thread):
    def run(self):
        mutexB.acquire()
        print('thread2--B上锁成功')
        time.sleep(1)
        mutexA.acquire()
        print('thread2--A上锁成功')
        mutexB.release()
        mutexA.release()

if __name__ == '__main__':
    T1 = thread1()
    T2 = thread2()
    T1.start()
    T2.start()
    
# --结果:--
# thread1--A上锁成功
# thread2--B上锁成功

4.3 避免死锁

  • 程序设计时要尽量避免
  • 添加超时时间等

5.Queue线程

相信大家对于数据结构中的栈和队列都不陌生吧,栈在这里我们不做介绍,我们就看看队列Queue

  • 在线程中,访问⼀些全局变量,加锁是⼀个经常的过程。如果你是想把⼀些数据存储到某个队列中,那么Python内置了⼀个线程安全的模块叫做queue模 块。Python中的queue模块中提供了同步的、线程安全的队列类,包括 FIFO(先进先出)队列Queue,LIFO(后⼊先出)队列LifoQueue。这些队列都实现了锁原语(可以理解为原⼦操作,即要么不做,要么都做完),能够在多线程中直接使⽤。可以使⽤队列来实现线程间的同步。其相关方法如下:
方法 功能
empty() 判断队列是否为空
full() 判断队列是否满
put() 向队列中放入元素
get() 从队列中取出元素
qsize() 获取队列长度
from queue import Queue

q = Queue(3)

# 判断队列是否为空
print(q.empty())        #True

# 判断队列是否满了
print(q.full())         #False

# 放入元素
q.put(111)
q.put(444)
q.put(666)
# 设置timeout或put_nowait当队列满了直接跳出
# q.put(886,timeout=2)

# 获取队列长度
print(q.qsize())        #3

# 获取元素
print(q.get())          #111
print(q.get())          #444
print(q.get())          #666

6.线程同步的实现

例如实现以下会话:
老师:小明?
小明:到。
老师:给我讲讲同步线程的实现吧!
小明:我…试试吧!

代码如下:

import threading

class XiaoMing(threading.Thread):
    def __init__(self,cond):
        super().__init__(name='小明')
        self.cond = cond
    def run(self):
        self.cond.acquire()
        self.cond.wait()
        print('{}:在。'.format(self.name))
        self.cond.notify()

        self.cond.wait()
        print('{}:我...试试吧!'.format(self.name))
        self.cond.notify()
        self.cond.release()

class Teacher(threading.Thread):
    def __init__(self,cond):
        super().__init__(name='老师')
        self.cond = cond
    def run(self):
        self.cond.acquire()
        # self.cond.wait()
        print('{}:小明?'.format(self.name))
        self.cond.notify()

        self.cond.wait()
        print('{}:给我讲讲同步线程的实现吧!'.format(self.name))
        self.cond.notify()
        self.cond.release()

if __name__ == '__main__':
    cond = threading.Condition()

    teacher = Teacher(cond)
    xiaoming = XiaoMing(cond)

    xiaoming.start()
    teacher.start()

ps:一定要把先进行的对话放在后面启动。否则会导致堵塞。

你可能感兴趣的:(python,多线程)