僵尸和孤儿进程、守护进程、进程和线程互斥锁、队列、进程池和线程池、回调函数
一、子进程回收资源的两种方式
- 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源。
- 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源。
二、僵尸进程和孤儿进程
僵尸进程 (有坏处):
- 在子进程结束后,主进程没有正常结束, 子进程PID不会被回收。
缺点:
- 操作系统中的PID号是有限的,如有子进程PID号无法正常回收,则会占用PID号。
- 资源浪费。
- 若PID号满了,则无法创建新的进程。
孤儿进程(没有坏处):
- 在子进程没有结束时,主进程没有“正常结束”, 子进程PID不会被回收。
- 操作系统优化机制(孤儿院):
当主进程意外终止,操作系统会检测是否有正在运行的子进程,会他们放入孤儿院中,让操作系统帮你自动回收。
from multiprocessing import Process from multiprocessing import current_process # 在子进程中调用,可以拿到子进程对象.pid可以pid号 # 在主进程中调用,可以拿到主进程对象.pid可以pid号 import os import time # 任务 def task(): print(f'start....{current_process().pid}') time.sleep(5) print(f'end......{os.getpid()}') print('子进程结束啦~~~') if __name__ == '__main__': p = Process(target=task) # 告诉操作系统帮你开启子进程 p.start() # p.join() print(f'进入主进程的IO-->{current_process().pid}') time.sleep(1) print(f'进入主进程的IO-->{os.getpid()}') # 主进程结束 print('主进程结束....') print(f'查看主主进程{os.getppid()}') f = open('tank.txt')
三、守护进程
当主进程结束时,子进程也必须结束,并回收。
from multiprocessing import Process # 在子进程中调用,可以拿到子进程对象.pid可以pid号 # 在主进程中调用,可以拿到主进程对象.pid可以pid号 import time # 任务 def demo(name): print(f'start....{name}') time.sleep(1000) print(f'end......{name}') print('子进程结束啦啊....~~~') if __name__ == '__main__': p = Process(target=demo, args=('童子军jason1号', )) # 守护进程必须在p.start()调用之前设置 p.daemon = True # 将子进程p设置为守护进程 # 告诉操作系统帮你开启子进程 p.start() # p.join() time.sleep(1) print('皇帝驾崩啦啊~~~')
from multiprocessing import Process def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") p1=Process(target=foo) p2=Process(target=bar) p1.daemon=True p1.start() p2.start() time.sleep(0.1) print("main-------")#打印该行则主进程代码结束,则守护进程p1应该被终止.#可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止.
四、进程间数据时隔离的
from multiprocessing import Process n=100 def work(): global n n=0 print('子进程内: ',n) if __name__ == '__main__': p=Process(target=work) p.start() print('主进程内: ',n)
五、进程互斥锁
互斥锁是一把锁,用来保证数据读写安全的。
from multiprocessing import Process,Lock import time,json,random def search(): dic=json.load(open('db')) print('\033[43m剩余票数%s\033[0m' %dic['count']) def get(): dic=json.load(open('db')) time.sleep(random.random()) # 模拟读数据的网络延迟 if dic['count'] >0: dic['count']-=1 time.sleep(random.random()) # 模拟写数据的网络延迟 json.dump(dic,open('db','w')) print('\033[32m购票成功\033[0m') else: print('\033[31m购票失败\033[0m') def task(lock): search() lock.acquire() # 将买票这一环节由并发变成了串行,牺牲了运行效率但是保证了数据的安全 get() lock.release() if __name__ == '__main__': lock = Lock() for i in range(100): # 模拟并发100个客户端抢票 p=Process(target=task,args=(lock,)) p.start()
六、队列
- 先存放的数据,就先取出来。
相当于一个第三方的管道,可以存放数据。
应用: 让进程之间数据进行交互。
from multiprocessing import Queue q=Queue(3) # 创建一个最大只能容纳3个数据的队列 """ 常用方法 put ,get ,put_nowait,get_nowait,full,empty """ q.put(3) # 往队列中存放数据 q.put(3) q.put(3) q.put(3) # 如果队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列。如果队列中的数据一直不被取走,程序就会永远停在这里。 try: q.put_nowait(3) # 可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错。 except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去,但是会丢掉这个消息。 print('队列已经满了') # 因此,我们再放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了。 print(q.full()) # 判断队列中数据是否已存放满了 print(q.get()) # 从队列中获取数据 print(q.get()) print(q.get()) print(q.get()) # 同put方法一样,如果队列已经空了,那么继续取就会出现阻塞。 try: q.get_nowait(3) # 可以使用get_nowait,如果队列满了不会阻塞,但是会因为没取到值而报错。 except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去。 print('队列已经空了') print(q.empty()) # 判断队列中数据是否已经被全部取出
# 第二种 # q_obj1 = JoinableQueue(5) # q_obj1队列对象 # # 添加数据到队列中 # q_obj1.put('jason') # print('添加1个') # q_obj1.put('hcy') # print('添加1个') # q_obj1.put('hb') # print('添加1个') # q_obj1.put('zsb') # print('添加1个') # q_obj1.put('lh') # print('添加1个') # # # put: 只要队列满了,会进入阻塞 # # q_obj1.put('sean') # # print('sean into ') # # # put_nowait: 只要队列满了,就会报错 # # q_obj1.put_nowait('sean') # # # get: 只要队列中有数据,就能获取数据,若没有则会进入阻塞 # print(q_obj1.get()) # print(q_obj1.get()) # print(q_obj1.get()) # print(q_obj1.get()) # print(q_obj1.get()) # 第三种 # q_obj1 = queue.Queue(5) # q_obj1队列对象 # # 添加数据到队列中 # q_obj1.put('jason') # print('添加1个') # q_obj1.put('hcy') # print('添加1个') # q_obj1.put('hb') # print('添加1个') # q_obj1.put('zsb') # print('添加1个') # q_obj1.put('lh') # print('添加1个') # put: 只要队列满了,会进入阻塞 # q_obj1.put('sean') # print('sean into ') # put_nowait: 只要队列满了,就会报错 # q_obj1.put_nowait('sean') # get: 只要队列中有数据,就能获取数据,若没有则会进入阻塞 # print(q_obj1.get()) # print(q_obj1.get()) # print(q_obj1.get()) # print(q_obj1.get()) # print(q_obj1.get())
七、进程间通信(IPC机制)
基于队列实现进程通信
import time from multiprocessing import Process, Queue def f(q): q.put('hello') #调用主函数中p进程传递过来的进程参数 put函数为向队列中添加一条数据。 if __name__ == '__main__': q = Queue() # 创建一个Queue对象 p = Process(target=f, args=(q,)) #创建一个进程 p.start() print(q.get()) # 从队列中获取数据 p.join() from multiprocessing import Queue,Process def producer(q): q.put('hello big baby!') def consumer(q): print(q.get()) if __name__ == '__main__': q = Queue() p = Process(target=producer,args=(q,)) p.start() p1 = Process(target=consumer,args=(q,)) p1.start()
八、生产者与消费者
- 生产者: 生产数据的
- 消费者: 使用数据的
- 生产油条的有人总比吃油条的人少 ---> 生产数据跟不上 使用数据的人 ---》 供需不平衡
- 吃油条的人比生产的油条要少 ---> 使用数据的速度 跟不上 生产数据的速度
- 通过队列来实现,解决供需不平衡问题
from multiprocessing import Process,Queue import time,random,os def consumer(q): while True: res=q.get() time.sleep(random.randint(1,3)) print('%s 吃 %s' %(os.getpid(),res)) def producer(q): for i in range(10): time.sleep(random.randint(1,3)) res='包子%s' %i q.put(res) print('%s 生产了 %s' %(os.getpid(),res)) if __name__ == '__main__': q=Queue() #生产者们:即厨师们 p1=Process(target=producer,args=(q,)) #消费者们:即吃货们 c1=Process(target=consumer,args=(q,)) #开始 p1.start() c1.start() print('主')
九、线程
1.什么是线程?
进程: 资源单位。
线程: 执行单位。
线程与进程都是虚拟的概念,为了更好表达某种事物。
注意: 开启一个进程,一定会自带一个线程,线程才是真正的执行者。
2.为什么要使用线程?
节省资源的占用。
- 开启进程:
- 1) 会产生一个内存空间,申请一块资源。
- 2) 会自带一个主线程
- 3) 开启子进程的速度要比开启子线程的速度慢
- 开启线程
- 1) 一个进程内可以开启多个线程,从进程的内存空间中申请执行单位。
- 2) 节省资源。
- 开启三个进程:
- 占用三份内存资源
- 开启三个线程:
- 从一个内存资源中,申请三个小的执行单位
- IO密集型用: 多线程
- IO(时间由用户定):
- 阻塞: 切换 + 保存状态
- 计算密集型用: 多进程
- 计算(时间由操作系统定):
- 计算时间很长 ---> 切换 + 保存状态
注意: 进程与进程之间数据是隔离的,线程与线程之间的数据是共享的。
3.怎么使用线程?
from threading import Thread import time # # number = 1000 # # # 启动线程的方式一: # 任务1: # def task(): # global number # number = 100 # print('start...') # time.sleep(1) # print('end...') # # # if __name__ == '__main__': # # 开启一个子线程 # t = Thread(target=task) # t.start() # # t.join() # print('主进程(主线程)...') # print(number) # 启动线程的方式二: # class MyThread(Thread): # def run(self): # print('start...') # time.sleep(1) # print('end...') # # # if __name__ == '__main__': # # 开启一个子线程 # t = MyThread() # t.start() # # t.join() # print('主进程(主线程)...') # from threading import current_thread # # number = 1000 # # def task(): # global number # number = 100 # print(f'start...{current_thread().name}') # time.sleep(3) # print(f'end...{current_thread().name}') # # # if __name__ == '__main__': # # 开启一个子线程 # for line in range(10): # t = Thread(target=task) # # 加上守护线程: 主进程结束,代表主线程也结束,子线程有可能未被回收。 # t.daemon = True # t.start() # # # t.join() # print(f'主进程(主线程)...{current_thread().name}') # print(number)
十、线程互斥锁
from threading import Lock from threading import Thread import time lock = Lock() # 开启10个线程,对一个数据进行修改 number = 100 def task(): global number # lock.acquire() number2 = number # time.sleep(1) number = number2 - 1 # lock.release() if __name__ == '__main__': list1 = [] for line in range(100000000): t = Thread(target=task) t.start() list1.append(t) for t in list1: t.join() print(number) # 90
十一、线程池
线程池是用来限制 创建的线程数
# from concurrent.futures import ThreadPoolExecutor # import time # # # pool只能创建100个线程 # pool = ThreadPoolExecutor(100) # # # def task(line): # print(line) # time.sleep(10) # # # if __name__ == '__main__': # for line in range(1000): # pool.submit(task, line) # 通过并发(同步)爬虫某个网站的小视频 import requests import re # import os # import uuid # # # # 1.发送请求,获取响应数据 # def get_page(url): # response = requests.get(url) # if response.status_code == 200: # return response # # # # 2.解析并提取主页id号 # def parse_page(response): # ''' # https://www.pearvideo.com/video_1630253 # https://www.pearvideo.com/video_1630042 # ''' # # 将所有电影的详情页id号,匹配获取,并放到列表中 # id_list = re.findall('href="video_(.*?)"', response.text, re.S) # # print(len(id_list)) # id_list = list(set(id_list)) # # print(len(id_list)) # return id_list # # # def parse_detail(response): # ''' # srcUrl="https://video.pearvideo.com/mp4/adshort/20191206/cont-1630253-14671892_adpkg-ad_hd.mp4" # srcUrl="(.*?)" # ''' # mp4_url = re.findall('srcUrl="(.*?)"', response.text, re.S) # # print(mp4_url, 111111) # if mp4_url: # return mp4_url[0] # # # # 3.保存数据 # def save_movie(movie_url): # response = get_page(movie_url) # # movie_dir = r'D:\项目路径\python13期\day30\梨视频' # movie_path = os.path.join( # movie_dir, str(uuid.uuid4()) + '.mp4' # ) # # print(movie_path) # with open(movie_path, 'wb') as f: # for line in response.iter_content(): # f.write(line) # # # if __name__ == '__main__': # response = get_page('https://www.pearvideo.com/') # # # 解析提取所有电影详情页id号 # id_list = parse_page(response) # # print(id_list) # # # 循环拼接详情页链接 # for id_num in id_list: # url = f'https://www.pearvideo.com/video_{id_num}' # # print(url) # # # 往详情页发送请求, # detail_response = get_page(url) # # print(detail_response.text) # # # # 解析电影详情页,并提取视频的存放的地址 # mp4_url = parse_detail(detail_response) # print(mp4_url) # # # # 发送请求获取视频真实数据 # # movie_response = get_page(mp4_url) # # # response.content # save_movie(mp4_url) # 异步爬取梨视频 import requests import re import os import uuid from concurrent.futures import ThreadPoolExecutor pool = ThreadPoolExecutor(100) # 1.发送请求,获取响应数据 def get_page(url): print(f'发送get请求: {url}') response = requests.get(url) if response.status_code == 200: return response # 2.解析并提取主页id号 def parse_page(response): ''' https://www.pearvideo.com/video_1630253 https://www.pearvideo.com/video_1630042 ''' # 将所有电影的详情页id号,匹配获取,并放到列表中 id_list = re.findall('href="video_(.*?)"', response.text, re.S) # print(len(id_list)) id_list = list(set(id_list)) # print(len(id_list)) return id_list # 解析详情页,获取视频链接 def parse_detail(res): ''' srcUrl="https://video.pearvideo.com/mp4/adshort/20191206/cont-1630253-14671892_adpkg-ad_hd.mp4" srcUrl="(.*?)" ''' res2 = res.result() print(res2) movie_url = re.findall('srcUrl="(.*?)"', res2.text, re.S) print(movie_url) if movie_url: movie_url = movie_url[0] pool.submit(save_movie, movie_url) # 3.保存数据 def save_movie(movie_url): # time.sleep(1) # 获取响应数据的过程是IO操作 response = requests.get(movie_url) movie_dir = r'D:\项目路径\python13期\day30\梨视频' movie_path = os.path.join( movie_dir, str(uuid.uuid4()) + '.mp4' ) # print(movie_path) with open(movie_path, 'wb') as f: for line in response.iter_content(): f.write(line) if __name__ == '__main__': response = get_page('https://www.pearvideo.com/') id_list = parse_page(response) for id_num in id_list: # 每一个视频详情页 url = f'https://www.pearvideo.com/video_{id_num}' # 异步提交并爬取详情页任务 # add_done_callback(parse_detail): 将get_page任务结束后的结果,扔给parse_detail函数 # parse_detail函数接收的是一个对象,对象中的result()就是get_page函数的返回值。 pool.submit(get_page, url).add_done_callback(parse_detail) import datetime print(datetime.datetime.now()) # 21:54 ---> 18:45
TCP实现服务端并发
#server.py import socket from concurrent.futures import ThreadPoolExecutor server = socket.socket() server.bind( ('127.0.0.1', 9000) ) server.listen(5) # 1.封装成一个函数 def run(conn): while True: try: data = conn.recv(1024) if len(data) == 0: break print(data.decode('utf-8')) conn.send('111'.encode('utf-8')) except Exception as e: break conn.close() if __name__ == '__main__': print('Server is run....') pool = ThreadPoolExecutor(50) while True: conn, addr = server.accept() print(addr) pool.submit(run, conn)
#client.py import socket client = socket.socket() client.connect( ('127.0.0.1', 9000) ) print('Client is run....') while True: msg = input('客户端>>:').encode('utf-8') client.send(msg) data = client.recv(1024) print(data)
十二、回调函数
# add_done_callback # from concurrent.futures import ThreadPoolExecutor # import time # # 池子对象: 内部可以帮你提交50个启动进程的任务 # p_pool = ThreadPoolExecutor(50) # # # def task1(n): # print(f'from task1...{n}') # time.sleep(5) # return 'nana' # # # def get_result(tank): # # print(obj.__dict__) # # print(obj._result) # result = obj.result() # print(result) # # # if __name__ == '__main__': # n = 1 # while True: # # 参数1: 函数名 # # 参数2: 函数的参数1 # # 参数3: 函数的参数2 # # submit(参数1, 参数2, 参数3) # # add_done_callback(参数1),会将submit提交的task1执行的结果,传给get_result中的第一个参数,第一个参数是一个对象。 # obj=p_pool.submit(task1, n) obj.add_done_callback(get_result) # n += 1