并发编程
程序、进程、线程的基本概念
- 程序:由源代码组成的可执行文件
- 进程:程序运行资源(内存资源)分配的最小单位;一个进程下可以有多个线程,但是至少有一个线程(主线程);进程之间相互独立,互不影响,资源也不共享
-
创建多线程的两种方式
# 直接创建不使用多线程import timedef download():print('开始下载')time.sleep(2)print('下载结束')if __name__ == '__main__':download()
方法一
target:指定的是需要执行多线程任务的函数名 args:可以传递元组形式的参数给执行的函数
# 直接创建多线程# 步骤:# 1.创建线程# 2.启动线程import time, threadingdef download(filename):print(filename'开始下载')time.sleep(2)print(filename'下载结束')if __name__ == '__main__':# 使用循环创建3个线程for i in rang(3):# 创建线程t = threading.Tread(target=download, args=(i,))# 启动线程t.start()
方法二
# 使用类创建多线程(推荐)# 步骤:# 1.自定义一个类并继承Tread类# 2.覆写run()方法import time, threadingclass MyThread(threading.Tread):# 如果需要传参则需要定义初始化方法def __init__(self, filename):# 处理父类的init# 方法一:# threading.Tread.__init__(self)# 方法二:super().__init__()# super()就相当于threading.Treadself.filename = filename# 覆写run方法def run(self):print(self.filename, '开始下载')time.sleep(2)print(self.filename, '下载结束')if __name__ == '__main__':# 使用循环创建3个线程for i in rang(3):# 实例化对象(创建线程)t = MyTread(i)# 传参需要定义初始化方法并处理父类的init# 启动线程,会自动执行run()方法t.start()
线程名称
主要应用于调错
import threadingclass MyTread(threading.Tread):def run(self):# treading.current_thread:查看当前运行的线程# 输出结果为:<MyTread(Tread-1, started 4408)print('{}正在运行'.format(threading.current_thread()))# name属性查看线程的名称,默认为Tread-nprint('{}正在运行'.format(self.name))if __name__ == '__main__':# 可以在实例化对象是传入name属性,修改线程名称t = MyTread(name = 'file{}'.format('新名称'))t.start()
互斥锁
线程之间共享全局变量的安全性问题
import threading# 创建锁lock = threading.Lock()# 定义初始值,0为数值型,不可变类型value = 0# 定义加1函数def add_value():# 在函数中对全局变量中的不可变类型进行修改需要先globalglobal value# 加锁(哪里有问题在哪里加锁)lock.acquire()for i in range(100000):value += 1# 释放锁lock.release()print(value)# 在不加锁的情况下此时得出的结果会出现问题if __name__ == '__main__':for i in range(2):t = threading.Thread(target=add_value)t.start()
生产者和消费者模式
队列
队列在爬虫中的使用:
- 将爬取的URL存放到队列中,每次生产者进行爬取时,会从队列中取出URL进行请求
- 将生产者爬取到的数据存放到队列中,消费者从队列中取出数据进行保存
# 导入队列(先进先出)from queue import Queue# 创建Queue对象q = Queue(maxsize=4)# maxsize代表队列中的最大容量# put():向队列中添加元素q.put(1)q.put(2)q.put(3)q.put(4)# q.put(5)# 注意:如果队列满了,再添加数据,程序不报错,而是进入阻塞状态(等待从队列中取出元素)# get():取出队列中的元素print(q.get())print(q.get())print(q.get())print(q.get())# print(q.get())# 注意:如果队列空了,在获取数据,程序不报错,而是进入阻塞状态(等待箱队列中添加元素)# full():判断队列是否为满,返回布尔值# empty():判断队列是否为空,返回布尔值
线程池
Pool.map创建线程池
爬虫属于I/O(Input/Output,输入/输出)密集型程序所以使用多线程;涉及计算密集型程序需要使用多进程
multiprocessing下的dummy模块下的Pool类,用来实现线程池。这个线程池有一个map()方法,可以让线程池里面的所有线程都“同时”执行一个函数。
from multiprocessing.dummy import Pooldef calc(num):return num * num# 初始化含有3个线程的线程池pool = Pool(3)# 可迭代序列(列表、元组、集合、字典等)origin_num = [x for x in range(10)]# map(函数名, 可迭代序列)result = pool.map(calc, origin_num)print(f'计算0-9的平方分别为:{result}')pool.close()pool.join() # 这里要先关闭再JOIN。进程池中进程执行完后再关闭,如果注释,那么程序直接关闭
