python day 20: 线程池与协程

2019/11/1

资料来自老男孩教育

2. 线程

线程适用于IO密集流操作,线程是最小的执行单元

线程之间数据是共享的,共用一块内存

import threading :导入线程模块

t = threading.Thread(target=func,args=())

t.start()

t.join([timeout]),此处join是wait的意思,即主线程等待子线程结束,最多等2秒

主进程是否等待子线程

t.setDaemon(True/False)

线程锁:使用RLOCK

mutex = threading.RLock()创建锁

mutex.acquire()获得锁

mutex.release()释放锁

Event事件

event_obj = threading.Event()创建事件对象

event_obj.wait()等待状态,flag为False则阻塞所有的线程

event_obj.clear()将flag设置为False

event_obj.set()将flal设置为True

3. 进程

创建进程

import multiprocessing

if name=="main":

p = multiprocessing.Process(target=func,args=(),)

p.deamon = True/False(主进程是否等待子进程结束)

p.start()

p.join([timeout])主进程等待子进程结束,最多等待多少秒

进程之间数据不共享,每个进程使用独立内存

进程间数据共享的实现方式Manage或Array

Array

数组必须一开始就定义好数组的长度

数组的元素必须是统一的数据类型

Manage()

manage = Manage()

manage.dict()无长度限制,比Array方便,进程间数据通信使用manage.dict是最优选择。

进程池Pool

pool = multiprocessing.Pool(5),创建一个最多支持5个进程的进程池。

pool.apply(func,(1,))申请一个进程执行某个函数。每个任务是排队执行。每个进程有join执行。

pool.apply_async(func=Foo,args=(1,),callback=Bar),申请一个进程去执行Foo方法,将Foo方法的返回值作为实参赋值给Bar函数。每个任务是并发执行。更多是使用apply_async。每个进程没有join执行. 进程的deamon=True。

pool.close()关闭进程池,不再接收新请求。或pool.terminate()

pool.join(),进程池中进程执行完毕后主进程再关闭。

4. 协程:gevent模块,又叫微线程

必须安装greenlet模块与gevent模块

gevent代表高性能,当发IO请求时,尤其是网络IO请求时,使用协程

gevent.sleep(0)

gevent.joinall()

from gevent import monkey; monkey.patch_all()
import requests
import gevent def f(url):
print("url: %s"%url)
ret = requests.get(url)
data = ret.text
print(url,len(data)) gevent.joinall([
gevent.spawn(f,"https://juejin.im/post/5aa7314e6fb9a028d936d2a4"),
gevent.spawn(f,"https://www.cnblogs.com/lanxing0422/p/pythonday19.html"),
gevent.spawn(f,"http://www.baidu.com"),
]) # # 用于IO密集流
# def foo():
# print("foo")
# # 执行gevent的sleep方法
# gevent.sleep(0)
# print("foo again")
#
# def bar():
# print("bar")
# gevent.sleep(0)
# print("bar again")
#
# gevent.joinall([
# gevent.spawn(foo),
# gevent.spawn(bar),
# ])

5. 扩展

生产者消费者模型

队列:Queue

队列的特性:先进先出

q = queue.Queue(max)

q.put()

q.get()

q.get_nowait

q.put_nowait()

rabbit_m_queue开源的,特别牛逼的

6. 自定义线程池

python内部没有提供,需要自定义。

实际使用中,更多是基于线程池来使用,而不是创建一个单独的线程。

非常重要,以后会经常使用。

import threading
import queue
import time
import contextlib Stopevent = object() class ThreadPool(object):
'''
进程池
''' def __init__(self, max_num):
self.max_num = max_num
self.q = queue.Queue()
self.terminate_flag = False
# 真实创建的线程列表
self.generate_list = []
# 空闲中的线程列表
self.free_list = [] def generate_thread(self):
'''
创建一个线程执行任务
:return:
'''
t = threading.Thread(target=self.call)
t.start() def run(self, func, args, callback=None):
'''
线程池执行一个任务
:param func: 任务函数名
:param args: 任务函数所需参数元组
:param callback: 任务执行失败或成功后执行的回调函数
:return: 如果线程池已经终止,则返回True否则None
''' if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
self.generate_thread()
tuple_obj = (func, args, callback)
self.q.put(tuple_obj) def call(self):
'''
循环获取任务并执行
:return:
'''
# 获取当前线程并添加到列表中
current_thread = threading.currentThread()
self.generate_list.append(current_thread) # 从队列中取任务
event = self.q.get()
# 当该任务的类型不是Stopevent时,死循环
while event != Stopevent:
func, args, callback = event
status = True
try:
ret = func(*args)
except Exception as e:
status = False
ret = e # 当回调函数不为空时,就执行回调函数
if callback:
try:
callback(status, ret)
except Exception as e:
pass # self.free_list.append(current_thread)
# event = self.q.get()
# self.free_list.remove(current_thread)
with self.work_state(self.free_list,current_thread):
if not self.terminate_flag:
event = self.q.get()
else:
event = Stopevent else:
self.generate_list.remove(current_thread) def close(self):
num = len(self.generate_list)
while num:
self.q.put(Stopevent)
num -= 1 def terminate(self):
self.terminate_flag = True
# 终止线程,不清空队列
# max = len(self.generate_list)
# while max:
# self.q.put(Stopevent)
# max -= 1
# self.q.empty()
# 终止线程且清空队列
while self.generate_list:
self.q.put(Stopevent)
self.q.empty() @contextlib.contextmanager
def work_state(self,state_list,work_thread):
'''用于记录线程池中正在等待的线程数'''
state_list.append(work_thread)
try:
yield
finally:
state_list.remove(work_thread) def foo(i):
time.sleep(0.1)
print("当前i是>>>",i)
return i + 100 def call_back(status, ret):
print("ret:>>>", ret) pool = ThreadPool(10) for i in range(50):
'''
将任务放进队列中:
创建线程:
只有空闲线程列表为空且创建的线程数量小于最大线程数时才会创建线程。
从队列中取到任务,线程执行任务
'''
pool.run(func=foo, args=(i,), callback=call_back)
pool.close()
print("gene_list",len(pool.generate_list))

7. 实现多进程TCP服务器

serSocket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

重新设置套接字选项,重复使用绑定的信息

当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到SO_REUSEADDR选项。

from socket import *
from multiprocessing import *
# 处理客户端的请求并为其服务
def dealwithClient(newSocket):
while True:
recvData = newSocket.recv(1024)
if len(recvData)==0:
break
else:
print(recvData)
newSocket.close() def main():
serSock = socket(AF_INET,SOCK_STREAM)
# 设置套接字选项,使其可以重复使用绑定的信息
serSock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
bindAddr= ('',9091)
serSock.bind(bindAddr)
serSock.listen(5) try:
while True:
newSocket,clientAddr = serSock.accept()
p1 = Process(target= dealwithClient,args=(newSocket,))
p1.start()
# 因为已经向⼦进程中copy了⼀份(引⽤) ,
# 并且⽗进程中这个套接字也没有用处了
# 所以关闭
newSocket.close()
finally:
serSock.close() if __name__=='__main__':
main()

8. 实现多线程TCP服务器

from socket import *
from threading import *
# 处理客户端的请求并为其服务
def dealwithClient(newSocket):
while True:
recvData = newSocket.recv(1024)
if len(recvData)==0:
break
else:
print(recvData)
newSocket.close() def main():
serSock = socket(AF_INET,SOCK_STREAM)
# 设置套接字选项,使其可以重复使用绑定的信息
serSock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
bindAddr= ('',9091)
serSock.bind(bindAddr)
serSock.listen(5) try:
while True:
newSocket,clientAddr = serSock.accept()
p1 = Thread(target= dealwithClient,args=(newSocket,))
p1.start()
# 因为线程中共享这个套接字, 如果关闭了会导致这个套接字不可⽤,
# 但是此时在线程中这个套接字可能还在收数据, 因此不能关闭
# newSocket.close()
finally:
serSock.close() if __name__=='__main__':
main()

9. 协程greenlet和gevent

⽐线程更⼩的执⾏单元(微线程)

⼀个线程作为⼀个容器⾥⾯可以放置多个协程

只切换函数调用即可实现多任务,可以减少CPU的切换

协程⾃⼰主动让出CPU

使用生成器,只切换函数调用即可完成多任务切换

import time
def A():
while True:
print(“----A---”)
yield
time.sleep(0.5)
def B(c):
while True:
print(“----B---”)
c.next()
time.sleep(0.5)
if __name__==‘__main__’:
a = A() # 如果一个函数中有yield,返回值就是一个生成器
B(a)
# python中的greenlet模块对协程进行了封装(底层相当于yield)
# 安装模块: pip3 install greenlet
from greenlet import greenlet
import time
def t1():
while True:
print("........A........")
gr2.switch()
time.sleep(1)
def t2():
while True:
print("........b........")
gr1.switch()#调到上次执行的地方继续执行
time.sleep(1)
gr1 = greenlet(t1)#创建一个greenlet对象
gr2 = greenlet(t2)
gr1.switch()#此时会执行1函数

python还有⼀个⽐greenlet更强⼤的并且能够⾃动切换任务的模块 gevent.

原理是当⼀个greenlet遇到IO(指的是input output 输⼊输出)操作时, ⽐如访问⽹络, 就⾃动切换到其他的greenlet, 等到IO操作完成, 再在适当的时候切换回来继续执⾏.

io密集型和cpu密集型:一些进程绝大多数时间在计算上,称为计算密集型(CPU密集型),此时用多进程.

有一些进程则在input 和output上花费了大多时间,称为I/O密集型。比如搜索引擎大多时间是在等待(耗时操作),相应这种就属于I/O密集型。此时用多线程.

import gevent
def A():
while True:
print(".........A.........")
gevent.sleep(1)#用来模拟一个耗时操作
#gevent中:当一个协程遇到耗时操作会自动交出控制权给其他协程
def B():
while True:
print(".........B.........")
gevent.sleep(1)#每当遇到耗时操作,会自用转到其他协程
g1 = gevent.spawn(A) # 创建一个gevent对象(创建了一个协程),此时就已经开始执行A
g2 = gevent.spawn(B)
g1.join() #等待协程执行结束
g2.join() #会等待协程运行结束后再退出

python day 20: 线程池与协程,多进程TCP服务器的更多相关文章

  1. 进程池和线程池、协程、TCP单线程实现并发

    一.进程池和线程池 当被操作对象数目不大时,我们可以手动创建几个进程和线程,十几个几十个还好,但是如果有上百个上千个.手动操作麻烦而且电脑硬件跟不上,可以会崩溃,此时进程池.线程池的功效就能发挥了.我 ...

  2. 并发编程(六)——进程/线程池、协程、gevent第三方库

    进程/线程池.协程.gevent第三方库 一.进程/线程池 1.进程池 (1)什么是进程池 如果需要创建的子进程数量不大,可以直接利用multiprocess中的Process来创建.但是当需要创建上 ...

  3. python3下multiprocessing、threading和gevent性能对比----暨进程池、线程池和协程池性能对比

    python3下multiprocessing.threading和gevent性能对比----暨进程池.线程池和协程池性能对比   标签: python3 / 线程池 / multiprocessi ...

  4. 并发编程 --进、线程池、协程、IO模型

    内容目录: 1.socket服务端实现并发 2.进程池,线程池 3.协程 4.IO模型 1.socket服务端实现并发 # 客户端: import socket client = socket.soc ...

  5. 进程池与线程池、协程、协程实现TCP服务端并发、IO模型

    进程池与线程池.协程.协程实现TCP服务端并发.IO模型 一.进程池与线程池 1.线程池 ''' 开进程开线程都需要消耗资源,只不过两者比较的情况下线程消耗的资源比较少 在计算机能够承受范围内最大限度 ...

  6. Python 37 进程池与线程池 、 协程

    一:进程池与线程池 提交任务的两种方式: 1.同步调用:提交完一个任务之后,就在原地等待,等任务完完整整地运行完毕拿到结果后,再执行下一行代码,会导致任务是串行执行 2.异步调用:提交完一个任务之后, ...

  7. 并发编程(六)--进程/线程池、协程、gevent第三方库

    一.进程/线程池 1.进程池 (1)什么是进程池 如果需要创建的子进程数量不大,可以直接利用multiprocess中的Process来创建.但是当需要创建上百个或上千个,手动创建就较为繁琐,这时就可 ...

  8. concurrent.futures进线程池和协程

    concurrent.futures 异步执行进程线程池的模块,一个抽象类,定义submit,map,shutdown方法 from concurrent.futures import Process ...

  9. 并发编程中死锁、递归锁、进程/线程池、协程TCP服务器并发等知识点

    1.死锁 定义; 类似两个人分别被囚禁在两间房子里,A手上拿着的是B囚禁房间的钥匙,而B拿着A的钥匙,两个人都没法出去,没法给对方开锁,进而造成死锁现象.具体例子代码如下: # -*-coding:u ...

随机推荐

  1. 最全Linux应急响应技巧 【转】

    概述 Linux环境下处理应急响应事件往往会更加的棘手,因为相比于Windows,Linux没有像Autorun.procexp这样的应急响应利器,也没有统一的应急响应处理流程.所以,这篇文章将会对L ...

  2. TP 控制器和模型里面order 写法不同

      控制器: Db::table('think_user')->where('status=1')->order('id desc')->limit(5)->select(); ...

  3. 常见 SQL 语句的加锁分析

    参考: https://www.aneasystone.com/archives/2017/12/solving-dead-locks-three.html

  4. Sketch2Code - Transform sketches into HTML using AI

    Sketch2Code - Transform sketches into HTML using AI https://sketch2code.azurewebsites.net/generated- ...

  5. spring boot允许跨域(CORS)的配置

    添加@Configuration配置类即可. @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { ...

  6. 基于Vue SEO的四种方案

    基于Vue SEO的四种方案 https://segmentfault.com/a/1190000019623624?utm_source=tag-newest

  7. 登陆服务器提示“You need to run "nvm install N/A" to install it before using it.”

    一.登陆服务器提示“You need to run "nvm install N/A" to install it before using it.” 二.执行命令: nvm ls ...

  8. JAVA中使用LDAP登录的三种方式

    搜索中关于java 登录ldap,大部分会采用  cn=xxx,ou=xxx,dc=xxx的方式,此处的cn是用户的Display Name,而不是account,而且如果ou有多层,比如我们的OU就 ...

  9. base64加密后无法解密

    记录一个问题: 使用java,或者命令行 base64 命令加密图片文件成加密数据后无法还原成图片 深入:使用java base64工具(sun base64或bouncycastle)加密的数据替换 ...

  10. Python的Colorama模块

    简介 Python的Colorama模块,可以跨多终端,显示字体不同的颜色和背景,只需要导入colorama模块即可,不用再每次都像linux一样指定颜色. 1. 安装colorama模块 1 pip ...