进程\线程协程
# 进程\线程协程
## 进程
### 概念
- 运行中的程序就是一个进程,CPU把文件程序运行起来,是计算机中最小的资源分配单位,主进程负责回收子进程的资源,主程序才能执行完
### 进程缺点
- 进程开启时间开销大
- 进程切换时间开销大
- 进程关闭时间开销大
### 进程优点
- 可以同时执行多个任务,数据隔离
- 进程里可以开线程
### 进程三状态
- 就绪
- 运行
- 阻塞
- cpu不工作
### windows操作系统和linux操作系统创建开启进程不同
- windows
- 实际新的子进程需要通过import父进程来完成数据导入,所一些内容我们只希望在父进程中完成的就写在if __name__ == '__main__':下面(典型socket)
- linux,ios
- 新的子进程直接copy父进程的数据,不在执行
### multiprocessing
- multiprocessing程创建开启方法
- 函数方式
from multiprocessing import Process
def func(conn): #子进程函数
print("这里是子线程函数执行代码,传入的变量%s" %conn)
if __name__ == '__main__':
while True:
conn =100
p = Process(target=func,args=(conn,)) #创建子进程
p.start() #开始子进程程
- 用class类方式
from multiprocessing import Process
#创建子进程类
class MyServer(Process):
def __init__(self,conn):
self.conn = conn
super().__init__()
#这里是子进程执行的函数
def run(self):
while True:
print("这里执行子进程代码")
if __name__ == '__main__':
#注意想socket绑定函数要放到man里面
p = MyServer(conn) #实例化子进程类
p.start() #开启子线程
- multiprocessing进程基本例程
- 函数方式
- server端
import socket
from multiprocessing import Process
import os
def chat(conn):
while True:
msg = conn.recv(2048)
conn.send(msg)
print(os.getpid())
if __name__ == '__main__':
sk = socket.socket()
sk.bind(("127.0.0.1", 9000))
sk.listen()
while True:
conn,addr = sk.accept()
p = Process(target=chat,args=(conn,))
p.start()
print("主进程id是%s" %os.getpid())
- client端
import socket
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(("127.0.0.1",9000))
while True:
inp = input(">>>")
sk.send(inp.encode("utf-8"))
msg = sk.recv(1024).decode("utf-8")
print(msg)
sk.close()
- 用class类方式
- server端
import socket
from multiprocessing import Process
import os
class MyServer(Process):
def __init__(self,conn):
self.conn = conn
super().__init__()
def run(self):
while True:
msg = self.conn.recv(2048)
self.conn.send(msg)
print("子进程id是%s" %os.getpid())
if __name__ == '__main__':
#windowes操作系统要放这里,mac,linux可以放main外面
sk = socket.socket()
sk.bind(("127.0.0.1", 9000))
sk.listen()
while True:
conn,addr = sk.accept()
p = MyServer(conn)
p.start()
print("主进程id是%s" %os.getpid())
- client端
import socket
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(("127.0.0.1",9000))
while True:
inp = input(">>>")
sk.send(inp.encode("utf-8"))
msg = sk.recv(1024).decode("utf-8")
print(msg)
sk.close()
- 进程几个基本方法
- p.join()
- 堵塞直到子进程结束,这是一个同步阻塞函数
- p.daemon()
- 设为守护模式,守护这个进程直到其它非守护进程结束这个进程才结束,是个异步非堵塞函数
- p.terminate()
- 杀死进程,异步非堵塞函数
- os.getpid()和p.ident
- 获取进程的PID
- p.is_alive()
- 判断进程是否活着
- 进程socket应用实现多人聊天
- server端
from multiprocessing import Process
import socket
import sys
import os
# monkey.patch_all()
def chat(conn,i,fn):
sys.stdin = os.fdopen(fn) #子进程中解决input输入报错问题语句
while True:
print("进入%s进程" %(i))
msg = conn.recv(1024).decode("utf-8")
print(msg)
#注意这里如果用的是windows操作系统输入会乱码,因为操作系统是gbk编码,pycharm是utf-8编码,可以把pycham改位gbk编码,linux没这个问题
inp = input(">>>")
conn.send(inp.encode("utf-8"))
i = 0
if __name__ == '__main__':
#在windows操作系统要把socket放在main函数里面,因为开子进程重新导入模块会报错
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.bind(("127.0.0.1", 9000))
sk.listen()
fn = sys.stdin.fileno() #子进程中解决input输入报错问题语句
while True:
print("进入主函数循环accept前")
conn, addr = sk.accept()
i+=1
print("进入主函数循环accept后")
p = Process(target=chat,args=(conn,i,fn)) #把函数加入线程中
p.start() #开启进程
- client端
import socket
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(("127.0.0.1",9000))
while True:
inp = input(">>>")
sk.send(inp.encode("utf-8"))
msg = sk.recv(1024).decode("utf-8")
print(msg)
sk.close()
- 进程聊天发送接收不堵塞
- server端
from multiprocessing import Process
import socket
import sys
import os
def chat(conn,i,fn):
sys.stdin = os.fdopen(fn) #子进程中解决input输入报错问题语句
while True:
print("进入%s进程" %(i))
msg = conn.recv(1024).decode("utf-8")
print(msg)
def chats(conn,i,fn):
sys.stdin = os.fdopen(fn) #子进程中解决input输入报错问题语句
while True:
print("进入%s进程" % (i))
inp = input(">>>")
conn.send(inp.encode("utf-8"))
i = 0
if __name__ == '__main__':
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.bind(("127.0.0.1", 9000))
sk.listen()
fn = sys.stdin.fileno() #子进程中解决input输入报错问题语句
while True:
print("进入主函数循环accept前")
conn, addr = sk.accept()
i+=1
print("进入主函数循环accept后")
p = Process(target=chat,args=(conn,i,fn)) #把函数加入线程中
p.start() #开启线程
p1 = Process(target=chats, args=(conn, i, fn)) # 把函数加入线程中
p1.start() # 开启线程
- client端
import socket
import sys
import os
def chat(conn,i,fn):
sys.stdin = os.fdopen(fn) #子进程中解决input输入报错问题语句
while True:
print("进入%s进程" %(i))
msg = conn.recv(1024).decode("utf-8")
print(msg)
def chats(conn,i,fn):
sys.stdin = os.fdopen(fn) #子进程中解决input输入报错问题语句
while True:
print("进入%s进程" % (i))
inp = input(">>>")
conn.send(inp.encode("utf-8"))
i = 0
if __name__ == '__main__':
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(("127.0.0.1", 9000))
fn = sys.stdin.fileno() #子进程中解决input输入报错问题语句
while True:
print("进入主函数循环accept前")
i+=1
print("进入主函数循环accept后")
p = Process(target=chat,args=(sk,i,fn)) #把函数加入线程中
p.start() #开启线程
p1 = Process(target=chats, args=(sk, i, fn)) # 把函数加入线程中
p1.start() # 开启线程
p.join()
p1.join()
### 进程之间的通信
- IPC机制(inter process communication)
- 常见的IPC机制
- 队列(Queue)和管道(Pipe)都是IPC机制
- 第三方软件
- redis
- 并发需求,速度快
- mencache
- 高可用
- kafka
- 断电保存数据,速度慢
- rabbimq
- 解耦
- 对列和进程之间数据安全
- 管道和进程之间数据不安全
- Queue
- 这个是Queue是multiprocessing中的
- 特点
- 先进先出
- 队列空时get列表的数据会堵塞
- 队列满时往列表中put数据会堵塞
- Queue的方法
- q = Queue()
- 创建一个Queue队列
from multiprocessing import Process,Queue
q = Queue(6) #创建一个长队为6的队列
q = Queue() #创建一个任意长的队列
- q.put(item)
- 往队列中放入数据,满是堵塞
from multiprocessing import Process,Queue
q = Queue(2)
q.put(1)
q.put(2) #队列满,堵塞
q.put(3)
结果:
列表中只有2个数1和2
- date = q.get()
- 从队列中取值,空时堵塞
from threading import Thread
from multiprocessing import Process,Queue
import time
q = Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
print("333333")
print(q.get()) #列表空,堵塞在这里
print("444444")
结果:
1
2
3
333333
- q.get_nowait()
- 列表位空不堵塞直接报错,并丢失数据,一般不用,用就需要左try异常处理
- 队列在进程中的应用例子
from multiprocessing import Process,Queue
def producer(q):
for item in range(5):
q.put(item)
def consumer(q):
while True:
get_date = q.get() #堵塞在这等待q队列有数据
print(get_date)
if __name__ == '__main__':
q = Queue(5)
p1 = Process(target=producer,args=(q,))
p1.start()
p2 = Process(target=consumer,args=(q,))
p2.start()
p1.join()
p2.join()
- 生产者和消费者模型
- 把一个生产数据并且处理数据的过程解耦
- 让生产的数据的过程和吹数据的过程达到一个工作效率上的平衡
- 中间的容器,在多进程那种我们使用队列或者被jion的队列,做到控制数据的量
- 当数据过剩的时候,队列的大小会控制者生产的行为
- 当数据严重不足的时候,队列会控制消费者的行为
- 我们还可以通过定期检查队列中元素的个数来调节生产者消费者个数
### Redis
### lock
- lock(互斥锁)
- 把一段代码程序锁住,一把锁不能在一个线程,进程中连续acquire,开销小
- 进程中要加锁的场景
- 共享数据资源(文件、数据库)
- 需要对资源进行修改、删除操作
- lock的函数方法
- lock = Lock()
- 创建一把锁
from multiprocessing import Process,Queue,Lock
lock = Lock()
- lock.acquire()
- 获取一把锁
- lock.release()
- 释放锁
- with lock:
- 获取一把锁,相当于lock.acquire()和lock.release()组合,这里会自动释放锁
- lock买票例子
from multiprocessing import Process,Queue,Lock
import json,time
def check_ticket():
with open("ticket_info.txt",mode="r",encoding="utf-8") as f:
dic = json.load(f)
ticket_count =dic.get("count")
print("还有余票%s" %ticket_count)
def buy_ticket(i):
with open("ticket_info.txt",mode="r",encoding="utf-8") as f:
dic =json.load(f)
ticket_count = dic.get("count")
if ticket_count>0:
print("第%s个人买到票" %i)
dic["count"]-=1
else:
print("没有余票")
with open("ticket_info.txt", mode="w", encoding="utf-8") as f:
json.dump(dic, f)
def task(lock,i): #把锁对象传给函数
check_ticket()
# with lock: #把下面代码加锁,这里可以用
# buy_ticket(i)
lock.acquire() #给函数加锁,这个可以用with lock替代,如上
buy_ticket(i)
lock.release() #释放锁
if __name__ == '__main__':
lock = Lock() #创建一把锁
for i in range(5):
p = Process(target=task,args=(lock,i))
p.start()
- Rlock(递归锁)
- 一把锁可以连续在一个线程中acquire多次,acquire多少次就要release多少次
- 创建递归锁
- moodle_lock=fork_lock = RLock
- 死锁现象
- 死锁概念
- 在某一些线程中出现陷入堵塞并且永远无法结束堵塞的情况就是死锁
- 出现死锁
- 一个线程中创建多把锁,而且锁交替使用
- 互斥锁在一个线程中连续acquire
- 避免死锁方法
- 在一个线程中只用一把锁,而且每一次acquire之后都要release
### 进程池
- 概念
- 预先开启固定个数的进程数,当任务来临的时候,直接交给已经开好的进程,让这个进程去执行就可以了,节省进程,线程的开启,关闭,切换的需要时间,减轻操作系统的负担
- ProcessPoolExecutor
- 方法
- p = ProcessPoolExecutor(池中线程/进程个数)
- 创建N个进程/线程数量的进程池
- ret = p.submit(函数,参数1,参数2)
- 异步提交任务
- tp..shutdown()
- 堵塞直到所有任务都执行完毕
- ret.result()
- 获取返回值,是个同步堵塞,所以取多个值时,先把利用列表把任务返回的对象接收完成后,再循环对象取值
- 例程
- 基本用法
from concurrent.futures import ProcessPoolExecutor
import random
import os
import time
def func():
print("start",os.getpid())
time.sleep(random.randint(1,3))
print("end", os.getpid())
if __name__ == '__main__':
p = ProcessPoolExecutor(5)
for i in range(10):
p.submit(func) #提交任务
p.shutdown() #关闭进程池,堵塞直到进程执行完成,再往下执行打印main
print("main",os.getpid())
- 传参和获取返回值
from concurrent.futures import ProcessPoolExecutor
import random
import os
import time
def func(i):
print("start",os.getpid())
time.sleep(random.randint(1,3))
print("end", os.getpid())
return i*100
if __name__ == '__main__':
p = ProcessPoolExecutor(5)
ret_list = []
for i in range(10):
ret = p.submit(func,i) #提交任务
ret_list.append(ret)
for i in ret_list:
# 获取结果,者是一个堵塞函数,不能放到上面提交循环,会产生同步,用列表接收后循环取值
result =i.result()
print(result)
# p.shutdown() #关闭进程池,堵塞直到进程执行完成,再往下执行打印main
print("main",os.getpid())
## 协程
### 协程概念
- 协程也可以称为微线程,就是开发者为了控制线程执行流程,控制一个任务切换到另一个函数中执行,程序员自己写的python代码。协程只是任务切换,本身协程不能实现并发,甚至会降低性能,但是协程+IO自动切换就提高
### 协程优缺点
- 优点
- 开销小,用户操作可控,完全不增加操作系统压力
- 缺点
- 不是(操作系统)调度控制,而是用户级别的,对IO感知比较低(很小时间切换有可能识别不到要切换)
- 不能应用多CPU
### gevent
- 基本应用
import gevent
import time
from gevent import monkey #必须导入这个模块
#必须执行这句代码,因为协程切换要阻塞,这句是把python其它阻塞变成gevent的阻塞,才能感知到
monkey.patch_all()
def eat():
print("林明均 is eating")
time.sleep(1)
print("林明均 finish eat")
def drink():
print("林明刚 is drinking")
time.sleep(1)
print("林明刚 finish drink")
g1 = gevent.spawn(eat)
g2 = gevent.spawn(drink)
gevent.joinall([g1,g2]) #这里必须加这句才能切换,因为协程必须有阻塞才能进行切换
结果:
林明均 is eating
林明刚 is drinking
林明均 finish eat
林明刚 finish drink
- import gevent
from gevent import monkey
monkey.patch_all() #必须执行这里
def func():
pass #函数里面必须有阻塞
g1 = gevent.spawn(func) #加入协程
gevent.joinall([g1,g2]) #加入阻塞,如果有就不必要加
- 协程socket应用实现多人聊天
- server端
#这个代码可以连接多个client端聊天,但是只能客户发信息后server端必须回复后才能接收到其它client信息
import socket
import gevent
import time
from gevent import monkey
monkey.patch_all()
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
def chat(conn,i):
while True:
print("进入%s协程" %(i))
msg = conn.recv(1024).decode("utf-8")
print(conn)
print(msg)
inp = input(">>>")
conn.send(inp.encode("utf-8"))
sk.bind(("127.0.0.1",9000))
sk.listen()
i = 0
while True:
print("进入连接主程序")
conn,addr = sk.accept()
print("接收到一个客户端连接后面")
print(conn,addr)
i+=1
gevent.spawn(chat,conn,i)
print("增加协程后面")
conn.close()
sk.close()
- client端
import socket
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(("127.0.0.1",9000))
while True:
inp = input(">>>")
sk.send(inp.encode("utf-8"))
msg = sk.recv(1024).decode("utf-8")
print(msg)
sk.close()
### asyncio
## 计算机操作系统
### 多道操作系统
- 数据隔离
- 连个任务分开执行
- 时空复用
- 能够在一个任务遇到IO操作时候,把CPU让出来给另一个任务去执行操作,缺点是操作系统切换任务的时候占用一定的时间
### 分时操作系统
- 通过时间片来操作,cpu轮转,每个程序分配一定的时间片,缺点:切换任务占用时间,降低CPU利用时间(速度慢),但是提高用户体验
### 多道操作系统+分时操作系统
### 实时操作系统
- CPU一直占用
### 通用操作系统
- 多道操作系统+分时操作系统+实时操作系统
- 多个任务在一台计算机上执行
- 一个程序遇到IO后cpu让出去
- 一个程序没有遇到IO,但是时间片到了切出去让出CPU
### 分布操作系统
- 把多个任务分配成不同小任务,给其它CPU工作,完后把结果返回来汇总
### 操作系统调度进程的算法
- 短作业优先
- 先来先服务
- 时间片轮转
- 多级反馈算法(汇集以上三种)
### 并行\并发
- 并发
- 一个CPU交替执行交替两个程序,看起来是两个同事执行,实际每个程序执行一段时间,是串行的
- 单线程并发有两种
- 协程IO并发:gevent
- 遇到IO堵塞就cpu就让出来给其它任务利用,待IO堵塞解除后接着从堵塞的位置继续执行,例如socket里面的accapt()和rev()函数
- 基于事件循环的异步非阻塞框架:Twisted框架(类似select模块)
- select IO模块
- 并行
- 两个CPU各自执行各自的程序,相互不干扰
### 同步\异步
- 同步
- 阻塞
- CPU不工作
- 同步概念
- 一个任务在执行,如果要执行另一个任务,必须停下第一个任务,去执行第二个任务,执行完了再回去执行第一个任务
- 非阻塞
- cpu工作
- 异步
- 异步阻塞
- 异步非阻塞
- 异步非阻塞是把任务设置为非堵塞模式,当执行遇到堵塞函数时候继续执行,一旦有满足条件就执行跳到回调函数执行回调函数.如socket里面可以sk.setblocking(False)设置位非阻塞
- 异步概念
- 执行一个任务,然后去调用另一个任务,调用的任务在执行,同时第一个任务也在执行,两个任务相互不影响
## 线程
### 概念
- 线程是计算机中能被CPU调度的最小单位,负责执行具体代码的,一个进程中至少有一个线程
### 优缺点
- 任务切换、创建线程、销毁线程也需要一些时间,但是远小于进程,线程数据共享
### threading
- threading线程创建开启方法
- 函数方式
from threading import Thread
def func(a,b):
pass
t = Thread(target=func,args=(a,b)) #创建线程
t.start() #开始线程
- 用class类方式
from threading import Thread
#创建子进程类
class MyServer(Thread):
def __init__(self,conn):
self.conn = conn
super().__init__()
#这里是子进程执行的函数
def run(self):
while True:
print("这里执行子进程代码")
if __name__ == '__main__':
t = MyServer(conn) #实例化子进程类
t.start() #开启子线程
- threading线程基本例程
- 函数方式
- 用类方式
- server端
import socket
from threading import Thread,current_thread
class MyServer(Thread):
def __init__(self,conn):
self.conn = conn
super().__init__()
def run(self):
while True:
msg = self.conn.recv(2048)
self.conn.send(msg)
print("子线程id是%s" %current_thread().ident)
if __name__ == '__main__':
sk = socket.socket()
sk.bind(("127.0.0.1", 9000))
sk.listen()
while True:
conn,addr = sk.accept()
t = MyServer(conn)
t.start()
print("主线程id是%s" %current_thread().ident)
- client端
import socket
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(("127.0.0.1",9000))
while True:
inp = input(">>>")
sk.send(inp.encode("utf-8"))
msg = sk.recv(1024).decode("utf-8")
print(msg)
sk.close()
- 进程几个基本方法
- t.join()
- 堵塞直到子线程结束,这是一个同步阻塞函数
- t.daemon()
- 设为守护模式,守护这个线程直到其它非守护线程结束这个进程才结束,是个异步非堵塞函数
- current_thread().ident
- 获取线程的PID
- t.is_alive()
- 判断线程是否活着
- 线程socket应用实现多人聊天
- server端
from threading import Thread
import socket
import time
def chat(conn,i):
while True:
print("进入%s线程" %(i))
msg = conn.recv(1024).decode("utf-8")
print(conn)
print(msg)
inp = input(">>>")
conn.send(inp.encode("utf-8"))
i = 0
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.bind(("127.0.0.1", 9000))
sk.listen()
while True:
print("进入主函数循环accept前")
conn, addr = sk.accept()
i+=1
print("进入主函数循环accept后")
p = Thread(target=chat,args=(conn,i)) #把函数加入线程中
p.start() #开启线程
# p.join() #阻塞等待进程P结束后才往下面执行
print("如过用了join就等待join子线程p结束后才执行到这里")
例子2
#用线程创建rsocket多个IP不同的serve连接
#客户端和上例子相同
from threading import Thread
import socket
import time
def chat(conn,i):
while True:
print("进入%s线程" %(i))
msg = conn.recv(1024).decode("utf-8")
print(conn)
print(msg)
# inp = input(">>>")
conn.send(msg.encode("utf-8"))
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.bind(("127.0.0.1", 9000))
sk.listen()
while True:
print("进入主函数循环accept前")
conn, addr = sk.accept()
print("进入主函数循环accept后")
p = Thread(target=chat,args=(conn,i)) #把函数加入线程中
p.start() #开启线程
# p.join() #阻塞等待进程P结束后才往下面执行
print("如过用了join就等待join子线程p结束后才执行到这里")
- client端
import socket
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(("127.0.0.1",9000))
while True:
inp = input(">>>")
sk.send(inp.encode("utf-8"))
msg = sk.recv(1024).decode("utf-8")
print(msg)
sk.close()
- 线程聊天发送接收不堵塞
- server端
from threading import Thread
import socket
def rev_msg(conn,i):
while True:
print("进入%s进程" %(i))
msg = conn.recv(1024).decode("utf-8")
print(msg)
def send_msg(conn,i):
while True:
print("进入%s进程" % (i))
inp = input(">>>")
conn.send(inp.encode("utf-8"))
i = 0
if __name__ == '__main__':
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.bind(("127.0.0.1", 9000))
sk.listen()
while True:
print("进入主函数循环accept前")
conn, addr = sk.accept()
i+=1
print("进入主函数循环accept后")
t = Thread(target=send_msg,args=(conn,i)) #把函数加入线程中
t.start() #开启线程
t1 = Thread(target=rev_msg, args=(conn, i)) # 把函数加入线程中
t1.start() # 开启线程
- client端
from threading import Thread
import socket
def rev_msg(conn,i):
while True:
print("进入%s进程" %(i))
msg = conn.recv(1024).decode("utf-8")
print(msg)
def send_msg(conn,i):
while True:
print("进入%s进程" % (i))
inp = input(">>>")
conn.send(inp.encode("utf-8"))
i = 0
if __name__ == '__main__':
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(("127.0.0.1", 9000))
while True:
print("进入主函数循环accept前")
i+=1
print("进入主函数循环accept后")
p = Thread(target=send_msg,args=(sk,i)) #把函数加入线程中
p.start() #开启线程
t1 = Thread(target=rev_msg, args=(sk, i)) # 把函数加入线程中
t1.start() # 开启线程
t1.join()
t1.join()
### Queue队列
- Queue
- 先进先出
#应用在写一个server,所有用户请求放在队列中
from queue import Queue,LifoQueue,PriorityQueue
v1 = Queue()
for initem in range(10):
v1.put(initem)
for outem in range(10):
print(v1.get(outem))
结果:
0
1
2
3
4
5
6
7
8
9
- LifoQueue
- 后进先出
#应用杂算法计算
from queue import LifoQueue
v2 = LifoQueue()
for initem in range(10):
v2.put(initem)
for outem in range(10):
print(v2.get(outem))
结果:
9
8
7
6
5
4
3
2
1
0
- PriorityQueue
- 优先输出
#优先级,常用在自动排序,常常用在抓取网页多个网页时顺序的的场景
from queue import PriorityQueue
v3 = PriorityQueue()
v3.put((1,"林明均1"))
v3.put((3,"林明均3"))
v3.put((2,"林明均2"))
print(v3.get())
print(v3.get())
print(v3.get())
结果:
(1, '林明均1')
(2, '林明均2')
(3, '林明均3')
### lock(进程里面以讲解)
- 互斥锁lock
- 递归锁Rlock
### 线程池
- 概念
- 预先开启固定个数的线程数,当任务来临的时候,直接交给已经开好的线程,让这个进程去执行就可以了,节省线程的开启,关闭,切换的需要时间,减轻操作系统的负担,比进程池开的数量大些
- ThreadPoolExecutor
- 方法
- t= ThreadPoolExecutor(池中线程/进程个数)
- 创建N个进程/线程数量的进程池
- ret = t.submit(函数,参数1,参数2)
- 异步提交任务
- t..shutdown()
- 堵塞直到所有任务都执行完毕
- ret.result()
- 获取返回值,是个同步堵塞,所以取多个值时,先把利用列表把任务返回的对象接收完成后,再循环对象取值
- 例程
- 基本用法
from concurrent.futures import ThreadPoolExecutor
from threading import currentThread
import random
import os
import time
def func():
print("start",currentThread())
time.sleep(random.randint(1,3))
print("end", currentThread())
if __name__ == '__main__':
t = ThreadPoolExecutor(5)
for i in range(10):
t.submit(func) #提交任务
t.shutdown() #关闭进程池,堵塞直到进程执行完成,再往下执行打印main
print("main",currentThread())
- 传参和获取返回值
from concurrent.futures import ThreadPoolExecutor
from threading import currentThread
import random
import os
import time
def func(i):
print("start",currentThread())
time.sleep(random.randint(1,3))
print("end", currentThread())
return i*100
if __name__ == '__main__':
t = ThreadPoolExecutor(10)
ret_list = []
for i in range(20):
ret = t.submit(func,i) #提交任务
ret_list.append(ret)
for i in ret_list:
# 获取结果,者是一个堵塞函数,不能放到上面提交循环,会产生同步,用列表接收后循环取值
result =i.result()
print(result)
# t.shutdown() #关闭进程池,堵塞直到进程执行完成,再往下执行打印main
print("main",currentThread())
- 带回调函数抓取网页
from concurrent.futures import ThreadPoolExecutor
import requests
def get_page(url):
res = requests.get(url)
return res.text
def parserpager(ret):
print(ret.result())
url_list = [
'https://www.jd.com/'
'https://douban.com'
'https://www.tencent.com'
]
if __name__ == '__main__':
t = ThreadPoolExecutor(10)
for url in url_list:
ret = t.submit(get_page,url)
# 回调函数,当提交到线程的get_page函数执行完后自动调用parserpager函数,并把返回的参数也传过去
ret.add_done_callback(parserpager)
## 制作多人聊天,可群聊历程
*XMind - Trial Version*