首页 / PYTHON / python -- 多任务编程
python -- 多任务编程
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了python -- 多任务编程,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含21382字,纯文字阅读大概需要31分钟。
内容图文
![python -- 多任务编程](/upload/InfoBanner/zyjiaocheng/643/016ae6c105cc4ca6b0e9903e18c3bd5b.jpg)
文章目录
一、 多任务编程
多任务含义
就是操作系统可以同时运?多个任务。
现实中的多任务比如:手机上边听音乐边聊天,电脑上多个软件同时运行
单核CPU实现多任务
操作系统轮流让各个任务交替执?,每个任务执?0.01秒,这样反复执?下去。 表?上看,每个任务交替执?,但CPU的执?速度实在是太快了,感觉就像所有任务都在同时执??样
逻辑上表现为:各个任务并发执行,即一个任务执行几秒后执行另一个任务,来回交替(同时运行,但是每个时间段只有一个任务执行)
物理上表现为:各个任务串行执行(第一个任务执行完之后再执行第二个任务)
多核CPU实现“多任务”
真正的并?执?多任务只能在多核CPU上实现,但是,由于任务数量远远多 于CPU的核?数量,所以,操作系统也会?动把很多任务轮流调度到每个核 ?上执?。
二、多进程编程
几个概念:
多进程编程:一个任务交给多个cpu
程序:编写完毕的代码,在没有运?的时候,称之为程序
进程:正在运?着的代码(程序),就成为进程
注意: 进程,除了包含代码以外,还有需要运?的环境等,所以和程序是有区别的
进程状态: 创建(created)-----就绪(ready)-----运行(running)-----阻塞(waiting)-----结束(terminated)
- 进程运行中cpu执行其他任务,造成进程中断,进入就绪状态,等待cpu分配时间片段
- 进行运行中需要等待一个输入,进入阻塞状态,接受输入,进入就绪阶段,等待cpu分配时间片段来running
创建进程
为什么需要多进程:当进入阻塞状态等待接受其他信息才能运行时,此时间片段浪费,可以进行其他任务使得时间合理化
多进程的条件:创建子进程
- Python的os模块中的fork()函数,用来创建子进程但是只能用在linux系统中
fork()函数理解:
执?到os.fork()时,操作系统会创建?个新的进程复制?进程的所有信息到?进程中
普通的函数调?,调??次,返回?次,但是fork()调??次,返回两次
?进程和?进程都会从fork()函数中得到?个返回值,?进程返回是0,??进程中返回?进程的 id号
思考问题:
fork拷贝父进程来创建子进程,修改全局变量,两个进程之间会有影响吗? fork到底对父进程拷贝了什么?
答:多进程修改全局变量,多进程中,每个进程中所有数据(包括全局变量)都各有拥有?份,父、子进程在内存的不同区域存储互不影响。
# 将当前进程使用fork拷贝一份子进程,同时在当前进程中定义一个全局变量money,在子进程中修改全局变量money,对比当前进程(父进程)与子进程的全局变量的值是否发生变化。
import os
import time
# 定义一个全局变量money
money = 100
print('当前进程pid:',os.getpid())
#为了更清楚的看到进程,让程序休眠
print('当前进程父id:',os.getppid()) #父进程为程序运行的大环境:pycharm
time.sleep(5)
p0bj=os.fork() #执行fork函数时:操作系统会将当前进程所有信息拷贝到?进程中
#fork()调??次,返回两次
#?进程和?进程都会从fork()函数中得到?个返回值,?进程返回是0,??进程中返回?进程的id号,用于记录父进程新创建的子进程
#?进程返回是0
if p0bj==0:
money = 200
print("子进程返回的信息, money=%d" % (money))
#?进程中返回?进程的pid
else:
print('创建子进程%s,父进程为%d'%(p0bj,os.getppid()))
print(money)
#执行结果:
创建子进程2508,父进程为11528
100
子进程返回的信息, money=200
- Windows没有fork调?,由于Python是跨平台的, multiprocessing模块就是跨平台版本 的多进程模块。
- multiprocessing模块提供了?个Process类来创建进程对象。
Process([group [, target [, name [, args [, kwargs]]]]])
target:表示这个进程实例所调?对象;就是进程要执行的任务
args:表示调?对象的位置参数元组;
kwargs:表示调?对象的关键字参数字典;
name:为当前进程实例的别名;
group:?多数情况下?不到; - Process类常??法:
is_alive() : 判断进程实例是否还在执?;
join([timeout]) : 等待当前进程执?完后主进程再继续执行;
start() : 启动进程实例(创建?进程);
run() : 如果没有给定target参数,对这个对象调?start()?法时,就将执 ?对象中的run()?法;
terminate() : 不管任务是否完成,?即终?; - Process类常?属性:
name:当前进程实例别名,默认Process-N,N为从1开始计数;
pid:当前进程实例的PID值
windows下多进程编程方法1: 实例化对象用于产生一个子进程。
Process:类
target参数:表示这个进程实例所调?对象;子进程要执行的任务
import time
from multiprocessing import Process
def task1(): #定义两个任务
print("正在听音乐")
time.sleep(1)
def task2():
print("正在编程......")
time.sleep(0.5)
def use_multi(): #使用多进程,需要在任务外部执行join
processes=[] #将所有任务创建好,再执行join等待
for i in range(2): #任务执行2此
p1 = Process(target=task1) #产生一个子进程,执行任务1
p1.start() #子进程执行任务
processes.append(p1)
for i in range(5):
p2 = Process(target=task2)
p2.start()
processes.append(p2)
#所有子进程任务创建好后,等待每一个进程的任务结束。
#开始第一个子进程的同时其他进程也在执行,直到一个一个的子进程全部结束
[process.join() for process in processes] 主进程阻塞,执行子进程,结束后再执行主进程。
if __name__ == '__main__':
# 主进程
start_time= time.time()
use_multi()
end_time = time.time()
print(end_time-start_time)
#3个进程同时执行:主进程、p1、p2, 并行
# 为了等两个子进程执行完后再执行主进程(计算2个子进程执行时间),使用join阻塞主进程,等p1、p2执行结束再执行主进程
对于join() 方法的理解:
在主进程的任务与子进程的任务彼此独立的情况下,
- 主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。
- 如果主进程的任务在执行到某一个阶段时,需要等待子进程完毕后才能继续执行,
就需要有一种机制能够让主进程检测子进程是否运行完毕,
在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用。
join()的作用:
在进程中可以阻塞主进程的执行, 直到等待子线程全部完成之后, 才继续运行主线程后面的代码
例如: join作用在使用多进程与不使用多进程的对比
def use_multi2():
p1 = Process(target=task1)
p2 = Process(target=task2)
#2个任务已经开始执行,阻塞当前进程,等待p1执行结束的同时也在执行p2任务;等待p2执行结束束再执行主进程。用来计算时间
p1.start() # p1任务开始执行,需要1s
p2.start() # p2任务开始执行,需要0.5s
p1.join() # p1还未执行结束,先行阻塞主进程,等p1执行结束,再执行后续代码,此时p2正在执行
p2.join() # p1执行结束,p2阻塞,等p2执行结束,再执行后续代码
def nouse_multi(): #相当于任务按顺序执行,执行完一个再执行下一个任务
p1 = Process(target=task1)
p2 = Process(target=task2)
#先开始p1任务,p1任务执行完再开启p2任务
p1.start() #p1任务开始执行,需要1s
p1.join() #p1还未执行结束,先行阻塞,等p1执行结束,再执行后续代码
p2.start() #p1任务执行结束,启动p2任务
p2.join() #等待p2任务执行结束,再回到主函数中执行后续代码
# p.join() 阻塞当前进程, 当p1.join()之后, p1就提示主进程, 需要等待p1进程执行结束才能向下执行, 那么主进程就乖乖等着, 自然不会执行p2.start()
windows下多进程编程方法二:创建子类,继承的方式
- 就是重写run() 方法,run()方法里就是要执行的任务
- 子类继承父类
- 任务在创建类的run方法里已经写了,实例化对象不需要再写任务
- 创建子进程需要加入参数时:字类定义构造方法,子类加入参数,先执行父类构造方法,再将对象和参数绑定应用于run方法中
from multiprocessing import Process
import time
class myprocess(Process):
""" 创建自己的进程,父类是Process"""
""" 当任务需要传参数时,在这里写"""
def __init__(self,music_name):
super(myprocess,self).__init__() # 重写构造方法 父类的构造函数
self.music_name = music_name
def run(self):
"""重写run方法,内容就是要执行的任务
start()默认执行run()方法
"""
print('听音乐%s' %(self.music_name))
if __name__ == '__main__':
for i in range(10):
p = myprocess('第%d首' %(i))
p.start()
p.join()
windows多进程编程方法三:使用进程池Pool
进程池Pool : 提供一定数量的进程去执行任务,如果进程都有任务(进程池为满状态),则其余任务为等待状态;进程没有任务时可直接分配任务。Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满, 那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。
进程池作用:进程需求大时、无需反复创建进程、销毁进程。
小案例:多进程判断素数。
数据量越大,进程池节省时间效果越明显,对比时间长度越小
from multiprocessing import Process
import time
# 判断1000~ 1200之间的素数
def is_prime(num):
if num == 1:
return True
for i in range(2, num):
if num % i == 0:
return False
else:
return True
def task(num):
if is_prime(num):
print('%d是素数' % (num))
def no_multi():
for num in range(1000, 1201):
task(num)
def use_multi():
processes = []
for num in range(1000, 1201):
# 实例化子进程对象
p = Process(target=task, args=(num,)) # 这个任务需要传参数,args是元组
# 开启子进程
p.start()
# 存储所有的子进程
processes.append(p)
# 阻塞子进程,等待所有子进程执行结束
for process in processes:
process.join()
def use_pool():
from multiprocessing import Pool
from multiprocessing import cpu_count
p = Pool(cpu_count()) #默认创建的进程数
p.map(task, list(range(1000, 1201))) #分配任务
p.close() # 关闭进程池对象
p.join()
if __name__ == '__main__':
start_time = time.time()
# no_multi() # 0.0009970664978027344
# use_multi() # 16.060956239700317 反而用的时间长了
use_pool() # 0.26380443572998047 当数据量非常非常大的时候,还是用pool节省时间
end_time = time.time()
print(end_time - start_time)
"""
使用多进程反而用的时间长了
因为开启的进程太多,我只有4核4个cpu,这样分配给每个cpu的也很多了,
还要进行复制那么多,所以其实效率没有那么高
不要开启太多进程,创建子进程会耗费时间和空间(内存空间)
"""
进程间通信
(1)进程间通信的目的:
数据传输、共享数据、通知事件、资源共享、进程控制
(2)进程间通信的方式:
管道、信号、信号量、消息队列、套接字
- 举例:
管道符,前一个命令的输出是后一个命令的输入
消息队列:外部数据结构队列,一个进程将传递消息依次放入队列中,另一个进程直接读取队列 - 通信方式分类:
不同主机进程通信: 套接字
同一台主机进程通信:管道、信号、信号量、消息队列
进程通信之实现消息队列:
可以使?multiprocessing模块的Queue实现多进程之间的数据传递,Queue 本身是 ?个消息列队程序。
Queue.qsize() : 返回当前队列包含的消息数量;
Queue.empty() : 如果队列为空,返回True,反之False ;
Queue.full() : 如果队列满了,返回True,反之False
Queue.get([block[, timeout]]) :出队,block默认值为True(阻塞,如果没有数据,一直等待,等到有数据了,出队)
Queue.get_nowait() : 相当Queue.get(False)(不阻塞)
Queue.put(item,[block[, timeout]]) :?队,block默认值 为True(如果队列满了不能入队,一直等待,等待有位置可以入队了就入队)
Queue.put_nowait(item) : 相当Queue.put(item, False)入队队列满直接报错不放消息
"""
生产者消费者模型:
一个进程用于生产数据,一个进程用于读取数据
主进程中:实例化一个队列,实例化2个进程并传入队列
"""
from multiprocessing import Process
from multiprocessing import Queue
import time
class Producer(Process):
def __init__(self,queue):
super(Producer, self).__init__()
self.queue = queue
def run(self):
"""将需要通信的数据写入队列中"""
for i in range(10):
self.queue.put(i)
time.sleep(0.1)
print('进程A向进程B传递信息,内容为%s' %(i))
class Consumer(Process):
def __init__(self,queue):
super(Consumer, self).__init__()
self.queue = queue
def run(self):
while True:
time.sleep(0.1)
data = self.queue.get()
print('进程B接收到进程A传递的信息:',data)
if __name__ == '__main__':
queue = Queue()
p1 = Producer(queue)
c1 = Consumer(queue)
p1.start()
c1.start()
p1.join()
c1.join()
三 多线程编程
线程含义
理解:程序中的一个函数,进程包含线程
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在 进程之中,是进程中的实际运作单位。
每个进程至少有一个线程,主线程(main函数)
同一个进程下不同线程之间共享同一个内存区域,访问的数据是一样的
进程可以启动多个线程。操作系统像并行“进程”一样执行这些线程。
线程的几种状态:
线程和进程的区别
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间.
- 进程之间的通信需要以通信的方式(IPC)进行。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,难点:处理好同步与互斥。
线程分类: 内核线程、用户线程或用户空间线程
内核线程是操作系统的一部分,而内核中没有实现用户空间线程
多线程编程
跟多进程类似
python的thread模块是?较底层的模块,python的threading 模块是对thread做了?些 包装的,可以更加?便的被使?
import threading
if __name__ == '__main__':
# 一个进程中一定有一个线程,叫主线程,用来管理其他线程
print('当前线程个数:',threading.active_count()) #当前线程个数。运行这个程序就是一个进程,一个进程中肯定有一个线程
print('当前线程信息:',threading.current_thread())
多线程编程方法一:通过实例化对象的方式实现多线程
多任务时,让多个线程执行,提高速率
理解:一个任务一个人执行需要10小时,一个任务10个人执行需要1小时
"""
多个线程对此任务执行多次
执行一次任务开启一个线程
计算时间,子线程执行完后执行主线程
理论上听5次音乐时间为5s,5个线程执行听5次音乐时间为1s
"""
import time
import threading
def task():
"""当前要执行的任务"""
print('听音乐。。。')
time.sleep(1)
if __name__ == '__main__':
start_time = time.time()
threads = []
for count in range(5):
t = threading.Thread(target=task)
t.start()
threads.append(t)
# 等待所有的子线程执行结束
for thread in threads:
thread.join()
end_time = time.time()
print(end_time - start_time)
小案例:以多线程方式给一个IP,提供归属地 ,将信息转换从json格式转换为python,存入数据库
API:应用程序接口,发送消息返回需要的内容
"""
IP地址归属地批量查询任务
并将信息存入数据库中
"""
import requests
import json
import sqlalchemy
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
import pymysql
from threading import Thread
pymysql.install_as_MySQLdb()
from sqlalchemy.orm import sessionmaker
def task(ip):
"""获取指定ip的所在城市和国家并存储到数据库中"""
"""
获取网页返回的内容
是json字符串
{"status":"success","country":"China","countryCode":"CN","region":"BJ","regionName":"Beijing","city":"Beijing","zip":"","lat":39.9042,"lon":116.407,"timezone":"Asia/Shanghai","isp":"China Unicom Shandong Province network","org":"NanJing XinFeng Information Technologies, Inc.","as":"AS56046 China Mobile Communications Corporation","query":"114.114.114.114"}
"""
url = 'http://ip-api.com/json/%s' %(ip)
#获取网址会出现问题,出错报错,不出错返回信息
try:
response = requests.get(url)
except Exception as e:
print('网页获取错误',e)
else:
contenpage = response.text # 返回文本信息
#将json字符串转化为字典
data_dict = json.loads(contenpage)
#获取城市和国家信息。获取city对应的value值,如果为空返回null,便于存入数据库
city = data_dict.get('city','null') #没有的话,返回None
country = data_dict.get('country','null')
#存储到数据库表中,添加数据信息,也就是实例化
ip = IP(ip = ip, city = city, country = country)
session.add(ip)
session.commit()
if __name__ == '__main__':
# 创建数据库引擎
engine = sqlalchemy.create_engine("mysql://root:root@192.168.1.104/lianxi",
encoding='utf8', echo=True)
# 建立缓存
session = sessionmaker(bind=engine)()
# 创建数据库对象需要继承的基类,
Base = declarative_base()
# 创建类,实际就是创建数据库表
class IP(Base):
__tablename__ = 'ips'
id = Column(Integer, primary_key=True, autoincrement=True)
ip = Column(String(20))
city = Column(String(30))
country = Column(String(30))
# 可视化
def __repr__(self):
return self.ip
# 创建数据库表
Base.metadata.create_all(engine)
threads = []
for item in range(10):
ip = '1.1.1' + str(item+1)
#使用多线程
t = Thread(target=task,args=(ip,))
t.start()
threads.append(t)
for th in threads:
th.join()
print('任务执行结束。。。')
print(session.query(IP).all()) # 获取IP表里的所有信息
多线程实现方法2:创建字类
小案例:查看局域网中哪些ip被使用,防止创建ip冲突
- 如果要在本地网络中确定哪些地址处于活动状态或哪些计算机处于活动状态, 则可以使用此脚本。我们将依次ping地址, 每次都要等几秒钟才能返回值。
没有线程的解决方案效率高,因为脚本必须等待每次ping
"""
判断一个ip开启一个线程
"""
import os
from threading import Thread
class GetHostAliveThread(Thread):
"""
创建子线程,执行的任务:判断指定的IP是否存活
"""
def __init__(self,ip):
super(GetHostAliveThread, self).__init__()
self.ip = ip
def run(self):
"""
重写run()方法:判断指定的IP是否存活
#执行shell命令行语句
os.system() 返回值为0:命令正确执行,不报错 返回值不为0:执行报错
os.system('ping -c1 -w1 172.25.254.49 &> /dev/null') # 不需要的信息打入垃圾箱。&> /dev/null 不显示信息
0 # 可以ping通
os.system('ping -c1 -w1 172.25.254.1 &> /dev/null')
256 # 不可以ping通
"""
# 执行的shell命令
cmd = 'ping -c1 -w1 %s ' %(self.ip)
result = os.system(cmd)
if result != 0:
print('%s主机没有ping通' %(self.ip))
if __name__ == '__main__':
print('打印172.25.254.0网段没有使用的IP地址'.center(10,'*'))
for i in range(1,255):
ip = '172.25.254.' + str(i)
thread = GetHostAliveThread(ip)
thread.start()
GIL全局解释器锁
多线程编程特点:
-
共享全局变量
优点: 在?个进程内的所有线程共享全局变量,能够在不使?其他?式的前提下完成多线程之间的数据共享(这点要?多进程要好) -
缺点: 线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程?安全)
如何解决线程不安全问题?
当需要对全局变量修改时:
线程同步:实现线程锁的过程,先对一个线程加锁,线程执行结束再解锁,此时其余线程不可对数据进行操作;
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作.
GIL(global interpreter lock): python解释器(CPython)中任意时刻都只有一个线程在执行;
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控 制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程 在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的 访问由全局解释器锁GIL来控制,正是这个锁能保证同一时刻只有一个 线程在运行。
线程同步和线程锁
来看这样一个现象
from threading import Thread
money = 0
def add():
for i in range(1000000):
global money
money += 1
def reduce():
for i in range(1000000):
global money
money -= 1
if __name__ == '__main__':
t1 = Thread(target=add)
t2 = Thread(target=reduce)
t1.start()
t2.start()
t1.join()
t2.join()
print(money)
运行结果:114831
原因解释:
运行结果并不是我们想要的0
这是因为在数据量非常大的时候,在运算的过程中,多个线程可能会出现不同步的现象。
一开始money=0
- 当第一个线程add来的时候money += 1 ,money只是加了1,这个时候还没有将1赋给money,money仍然是0;
- 这时候第二个线程reduce来了,他直接在money还是0的基础上就去减1,因此结果并不是我们想要的
这是因为多线程共享全局变量
线程对全局变量随意遂改可能造成多线程之间对全局变量的混乱。
线程同步:
- 同步就是协同步调,按预定的先后次序进?运?。如:你说完,我再说。
"同"字应是指协同、协助、互相配合 - 在对数据进行更改时怎么实现线程同步?
需要用到线程锁:
实例化一个锁对象,主函数中
lock = threading.Lock()
#操作变量之前进行加锁
lock.acquire()
#操作变量之后进行解锁
lock.release()
# 实现线程同步
from threading import Thread
from threading import Lock
money = 0
def add():
for i in range(1000000):
global money
#操作变量之前进行加锁
lock.acquire()
money += 1
#操作变量之后进行解锁
lock.release()
def reduce():
for i in range(1000000):
global money
# 操作变量之前进行加锁
lock.acquire()
money -= 1
# 操作变量之后进行解锁
lock.release()
if __name__ == '__main__':
t1 = Thread(target=add)
t2 = Thread(target=reduce)
#实例化一个线程锁
lock = Lock()
t1.start()
t2.start()
t1.join()
t2.join()
print(money)
死锁
死锁: 多个线程互相争夺资源
在线程间共享多个资源的时候,如果两个线程分别占有?部分资源并且同时等待对?的资 源,就会造成死锁。
- 比如:A有1个苹果1个手机,B有1个梨1个电脑
A说:你先把你的梨给我,我就把我的苹果给你
但是B说:你得先把你的手机给我,我就把我的电脑给你
他俩都在等待对方给资源,僵持住了
"""转账操作"""
import time
import threading
class Account(object):
def __init__(self, id, money, lock):
self.id = id
self.money = money
self.lock = lock
def reduce(self, money):
self.money -= money
def add(self, money):
self.money += money
def transfer(_from, to, money):
if _from.lock.acquire(): # 加锁
_from.reduce(money)
# 让线程之间有一个争夺资源的过程,有一点时间的浪费
time.sleep(1)
if to.lock.acquire(): # 加锁
to.add(money)
to.lock.release()
_from.lock.release()
if __name__ == '__main__':
a = Account('a', 1000, threading.Lock())
b = Account('b', 1000, threading.Lock())
t1 = threading.Thread(target=transfer, args=(a, b, 200))
t2 = threading.Thread(target=transfer, args=(b, a, 100))
t1.start()
t2.start()
print(a.money)
print(b.money)
执行结果:
800
900
由于a给b转有一点时间的浪费,此时b也要给a转,互相等待资源,形成死锁。程序就一直结束不了,一直僵持不动了.
怎么解决死锁?
(1)不要用多个锁
(2)对每个锁进行优先级排序
四、协程
1、什么是协程
协程,又称微线程,纤程。英文名Coroutine。
协程看上去也是子程序,但执行过程中, 在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行
进程 — 线程 — 协程
可以对应理解为:
程序 — 函数 — 函数片段
在执行到一个函数的某个语句时,跳到另外一个函数的某个语句去执行,执行一会儿后又跳回到原来停止的地方执行
和yield类似
协程的优势
(1)执行效率极高,因为子程序切换(函数),不是线程切换,由程序自身控制,
(2)没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
(3)不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多
协程实现
可以通过yield去实现协程
import time
def consumer():
r = ''
while True:
n = yield r # 停止
if not n :
return
print('[consumer] Consuming %s...' %(n))
time.sleep(1)
r = '200 ok'
def produce(c):
c.next() # 执行consumer生成器内容
n = 0
while n < 5:
n = n + 1
print('[producer] Produceing %s...' %(n))
r = c.send(n) # 把n=1发送给consumer 15行
print('[producer] Consumer rerurn: %s' %(r))
c.close()
if __name__ == '__main__':
c = consumer() # consumer()函数里有yield ,所以返回的c是一个生成器
produce(c)
分析:
- c = consumer(),执行consumer函数,停在yield处,此时返回n=0给c,produce©,执行produce函数,
- c.next() 执行consumer 函数,停在yield,接着执行produce函数n+1=1,打印生产;
- r = c.send(n) ,c带着生产信息n=1 发送给 r 赋值给n,执行consumer的停止处yleid,此时n不为空,打印消费n,执行死循环停止到yleid
- 打印produce中的print函数,produce继续循环
yleid实现缺点:需自己分析在哪里跳转
gevent模块实现协程
基本思想: 当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到 IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO
gevents = [gevent.spawn()]
gevent.joinall(gevents)
在前面的案例:IP地址归属地批量查询任务中
使用协程改写
import gevent
#打补丁:实现协程功能时:自动发现当io操作等待时要切换,改变核心代码。
from gevent import monkey
monkey.patch_all()
gevents = [gevent.spawn(task,'1.1.1.'+str(ip+1)) for ip in range(10)]
gevent.joinall(gevents)
五、总结
多任务分类:
(1)IO密集型的任务
有阻塞状态,需要等待,不会一直占用CPU
建议使用多线程编程
(2)计算密集型的任务
没有阻塞状态,一直占用CPU
建议使用多进程编程
哦? 发布了49 篇原创文章 · 获赞 4 · 访问量 1339 私信 关注内容总结
以上是互联网集市为您收集整理的python -- 多任务编程全部内容,希望文章能够帮你解决python -- 多任务编程所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。