1.线程和进程概念
进程:是资源分配的最小单位,相当于公司老板,本身不干活但是为干活的员工提供资源和平台。
线程:是CPU调度的最小单位,相当于公司的员工,真正干活的人就是这些员工。
2.线程和进程的关系
1)一个线程只能属于一个进程,一个进程可以有多个进程,每个进程至少有一个线程(主线程)。
2)资源分配给进程,一个进程中的所有线程将共享该进程中所有资源。
3)线程在执行过程中,需要协作同步,不同进程间的线程需要利用消息同步机制来实现通讯。
4)真正执行任务的是线程,它是进程内可调度的实体。
3.线程和进程需要注意:
1)一个进程必须包含至少一个线程;
2)一个进程可以包含多个线程
3)不同进程间数据很难共享
4)同一进程下,不同线程间的数据则很容易共享
5)创建进程要比创建线程消耗更多的计算机资源
6)进程间不会相互影响,而相同进程内部如果有一个线程出现异常,则其他所有线程将全部挂掉。
7)同一进程内不同线程使用同一资源时需要加锁
4.比较重要的概念
1)串行:做完一件事再完后另一件事,完成任务是所有单个任务完后才能时间的总和。
2)并行:两个或多个事件在同一时刻发生,相当于多个人同时完成多件事,是物理意义上的并行。
3)并发:两个或多个事件在同一时间间隔内发生,相当于一个人同时做多件事,是逻辑意义上的并行。
5.线程相关的模块
_thread:这个模块是在python3之前thread模块的重命名,它是一个比较底层的模块,所以一般我们不会直接在代码中直接使用。
threading:python3之后的线程模块,编程中一般使用这个模块来创建线程。
创建线程的两种方式:一是通过继承于Thread类来创建新线程,二是通过Thread类构造器来创建新线程。
例子1:通过继承于Thread类来创建新线程,未等待子线程结束就直接执行了主线程
import threading import urllib.request import urllib.parse def get_web(url): user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' headers={ "User - Agent":user_agent } # 构造Request对象,以便我么设置http请求中的header req=urllib.request.Request('http://www.baidu.com',headers=headers) resp=urllib.request.urlopen(req) print(resp.read().decode()[:50]) if __name__ == '__main__': t1=threading.Thread(target=get_web,args=('https://www.baidu.com',)) t2=threading.Thread(target=get_web,args=('http://www.woniuxy.com',)) t1.start()#启动线程 t2.start() print('程序运行结束!')
join方法:让子线程阻塞主线程的执行过程,也就是使用了join之后,主线程会等待使用了join方法的子线程结束后再往下执行。
例子2:等子线程结束后才会执行主线程
import threading import urllib.request import urllib.parse def get_web(url): user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' headers={ "User - Agent":user_agent } # 构造Request对象,以便我么设置http请求中的header req=urllib.request.Request('http://www.baidu.com',headers=headers) resp=urllib.request.urlopen(req) print(resp.read().decode()[:50]) if __name__ == '__main__': t1=threading.Thread(target=get_web,args=('https://www.baidu.com',)) t2=threading.Thread(target=get_web,args=('http://www.woniuxy.com',)) t1.start()#启动线程 t2.start() t1.join() t2.join() print('程序运行结束!')
例子3:通过Thread类构造器来创建新线程,比较灵活
import threading import urllib.request import urllib.parse class MyThread(threading.Thread): def __init__(self,name,url): super().__init__()#调用父类的初始化方法,保证父类初始化成功 self.name= name self.url=url def run(self):#重写父类的run方法,定义在新的线程类里面要完成的task print('我是线程-{}'.format(self.name)) user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' headers = { "User - Agent": user_agent } # 构造Request对象,以便我么设置http请求中的header req = urllib.request.Request('http://www.baidu.com', headers=headers) resp = urllib.request.urlopen(req) print(resp.read().decode()[:50]) if __name__ == '__main__': t1=MyThread('线程1','https://www.baidu.com') t2=MyThread('线程2','http://www.woniuxy.com') t1.start()#启动线程 t2.start() t1.join() t2.join() print('程序运行结束!')
6.锁的概念
锁GIL=全局解释器锁,他是粗粒度的锁,必须配置线程模块中的锁才能对每个原子操作进行锁定。
GIL全局解释器锁在什么时候会释放:
1)当当前的执行线程在执行IO操作时,会主动放弃 GIL;
2)当当前执行的线程执行了100条字节码的时候,会自动释放GIL锁;
例子1:
注意:获取锁和释放锁必须是成对出现的
import threading num=0 lock=threading.Lock() def deposite(): for i in range(10000000): lock.acquire()#获取锁 global num num +=1 lock.release()#锁释放 def withdraw(): for i in range(10000000): lock.acquire() # 获取锁 global num num -= 1 lock.release() # 锁释放 if __name__=='__main__': t1=threading.Thread(target=deposite) t2 = threading.Thread(target=withdraw) t1.start() t2.start() t1.join() t2.join() print(num)
例子2:lock.acquire()#获取锁和lock.release() # 锁释放==with lock
import threading num=0 lock=threading.Lock() def deposite(): for i in range(10000000): # lock.acquire()#获取锁 with lock: global num num +=1 # lock.release()#锁释放 def withdraw(): for i in range(10000000): # lock.acquire() # 获取锁 with lock: global num num -= 1 # lock.release() # 锁释放 if __name__=='__main__': t1=threading.Thread(target=deposite) t2 = threading.Thread(target=withdraw) t1.start() t2.start() t1.join() t2.join() print(num)
7.线程间通讯:
线程间通讯的几种方式:
Event:主要用于通过事件通知机制四线线程的大规模并发
Condition:主要用于多个线程间轮流交替执行任务
Queue:主要用于不通过线程间任务类型数据的共享
q=Queue(maxsize=0)
#阻塞程序,等待队列消息
q.get()
#阻塞程序,设置超时时间
q.get(timeout=1)
#发送消息,将消息放入队列
q.put()
例子1:模拟消息通讯的过程
from queue import Queue import threading import time def product(q): kind=('aaaa','bbbb','cccc') for i in range(3): print(threading.current_thread().name,'生产的产品为。。。。') time.sleep(1) q.put(kind[i%3]) print(threading.current_thread().name,'生产的产品已完成!') def cousumer(q): while True: time.sleep(1) t=q.get() print('消费者消费的产品{}!'.format(t)) if __name__=='__main__': q=Queue(maxsize=1) #启动生产者线程 threading.Thread(target=product,args=(q,)).start() threading.Thread(target=product, args=(q,)).start() #启动消费者线程 threading.Thread(target=cousumer, args=(q,)).start()
8.创建多个进程
方式:一是一指定函数作为参数创建进程,二是继承Process类创建进程
例子1:指定函数作为参数创建进程
import urllib.request import multiprocessing#引入多进程模块 import urllib.parse def get_web(url): user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' headers={ "User - Agent":user_agent } # 构造Request对象,以便我么设置http请求中的header req=urllib.request.Request('http://www.baidu.com',headers=headers) resp=urllib.request.urlopen(req) print(resp.read().decode()[:50]) if __name__=='__main__': p1=multiprocessing.Process(target=get_web,args=('https://www.baidu.com',)) p2=multiprocessing.Process(target=get_web, args=('https://www.woniuxy.com',)) p1.start() p2.start() p1.join() p2.join() print('进程执行全部完成')
例子2:继承Process类创建进程
import urllib.request import multiprocessing#引入多进程模块 import urllib.parse import os class MyProcess(multiprocessing.Process): def __init__(self,name,url): super().__init__()#调用父类的初始化方法,保证父类初始化成功 self.name= name self.url=url def run(self):#重写父类的run方法,定义在新的进程类里面要完成的task print('我是进程-{}'.format(self.name),'当前进程ID为:',os.getpid(),'我的父进程ID为:',os.getppid()) user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' headers = { "User - Agent": user_agent } # 构造Request对象,以便我么设置http请求中的header req = urllib.request.Request('http://www.baidu.com', headers=headers) resp = urllib.request.urlopen(req) print(resp.read().decode()[:50]) if __name__=='__main__': print('当前主进程ID为:',os.getpid()) p1=MyProcess('子进程1','https://www.baidu.com') p2 = MyProcess('子进程1', 'https://www.woniuxy.com') p1.start() p2.start() p1.join() p2.join() print('进程执行全部完成')
9.多进程之间的通讯
例子1:设置的maxsize=1队列只能为1个
import multiprocessing import time def product(q): kind=('aa','bb','cc') for i in range(3): print(multiprocessing.current_process().name,'生产的产品为。。。。') time.sleep(1) q.put(kind[i%3]) print(multiprocessing.current_process().name,'生产的产品已完成!') def cousumer(q): while True: print(multiprocessing.current_process().name,'消费者准备。。。。') time.sleep(1) t=q.get() print('消费者消费的产品{}!'.format(t)) if __name__=='__main__': q=multiprocessing.Queue(maxsize=1)#创建多个进程队列对象, #启动生产者进程 p1=multiprocessing.Process(target=product,args=(q,)).start() P2=multiprocessing.Process(target=product, args=(q,)).start() #启动消费者进程 multiprocessing.Process(target=cousumer, args=(q,)).start()
10.多线程和多进程编程总结
多进程的优点:独立运行互不影响
多进程的缺点:创建进程的代价非常大
多线程的优点:效率比较高,不会耗费大量的资源
多线程的缺点:稳定性较差,一个奔溃后会影响整个进程。
多进程适用场景:对于计算密集型任务比较适合多进程
多线程适用场景:适合IO密集型任务,比如文件读取以及爬虫等操作