进程
程序: 应用软件,一堆代码文件.
进程: 一个正在执行的程序/文件,抽象的概念.
数据隔离 开启/调度/销毁开销大 能利用多核, 是资源分配的最小单位,适合处理高计算型的
1、 单个cpu:
多道技术:
空间上的复用:(内存可以加载很多个不同的任务)
时间上的复用:
1, 遇到进程中的IO时,cpu就切换
IO(input output):用户输入input,f.read()
目的:提高效率
2, 一个进程长时间被cpu处理时,操作系统就强硬的将cpu调出去处理下一个进程.
目的:雨露均沾
2、 串行:cpu将一些进程一个接一个的执行
并发:一个cpu在同一个时刻处理不同的进程,任务
并行:真正意义的一对一服务
阻塞:IO成为阻塞
非阻塞:进程没有IO,就非阻塞
同步:两个任务一个接一个的进行
异步:两个任务能够同时进行
一个进程三个状态
运行,阻塞,就绪
查看子进程和父进程
子进程: pid
父进程: ppid
1.查看进程号:
1).终端查看 tasklist
2).本文件也可以看
import time
import os
a = 1
b = 2
print('子进程:',os.getpid())
print('父进程:',os.getppid())
time.sleep(20)
print(a+b)
2.创建进程的两种方式
windows环境下想开启子进程一定要 name == 'mian
p = Process(target=task,kwargs={'n':1}) # p就是一个进程对象
创建一个进程对象,将进程的代码放进进程对象中.
p.start() 会给操作系统发送一个请求,
操作系统得到请求命令,会在内存开辟一个空间,操作系统会将主进程所有的代码数据copy一份到子进程,
.这个过程会有时间消耗.
print('主进程开始.....') 主进程开始 和子进程开始,谁先到内存被cpu执行,谁先运行
1)multiprocess模块
a = 1
b = 2
def aa(c):
print('%s is running'%c)
if __name__ == '__main__':
# p = Process(target=aa,args=(1,))
p = Process(target=aa,kwargs={'c':1})
p.start()
print('主进程') #1 is running
from multiprocessing import Process
class Myprocess(Process):
# 先要执行父类的__init__
def __init__(self,n):
super(Myprocess, self).__init__()
self.n = n
def run(self):
# 子进程的代码都要写在这里
a = 1
b = 2
print('%s is running'%self.n)
if __name__ == '__main__':
p = Myprocess('666')
p.start()
print('主线程')
3.验证进程间的内存隔离(改了就是没隔离)
from multiprocessing import Process
import time
x = 100
def task():
time.sleep(2)
global x
x = 3
print('子进程',x)
if __name__ == '__main__':
p = Process(target=task,)
p.start()
time.sleep(5)
print('主:',x) #结果:子进程 3 主: 100
4.主进程在子进程结束后运行
1)用time.sleep() 2)p.join()
from multiprocessing import Process
import time
x = 100
def task(x):
print('%s is begin'%x)
time.sleep(3)
print('%s is over'%x)
def main():
print('主:')
if __name__ == '__main__':
p = Process(target=task,args=(1,))
p.start()
p.join()
main()
5.一个主进程开启多个子进程( for循环)
十个子进程请求几乎是同同时发起,谁先到达操作系统,操作系统给谁开辟空间,让cpu执行.
from multiprocessing import Process
import time
x = 100
def task(x):
print('%s is begin'%x)
time.sleep(3)
print('%s is over'%x)
def main():
print('主:')
if __name__ == '__main__':
for i in range(1,11):
p = Process(target=task,args=(1,))
p.start()
p.join()
main()
6.这种方法执行效率高
1)
from multiprocessing import Process
import time
x = 100
def task(x):
print('%s is begin'%x)
time.sleep(3)
print('%s is over'%x)
def main():
print('主:')
if __name__ == '__main__':
p1 = Process(target=task,args=('p',))
p2 = Process(target=task,args=('p2',))
p3 = Process(target=task,args=('p3',))
start_time = time.time()
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
print(time.time()-start_time)
main() #结果: 3.2413246631622314秒(并发)
2)这种方法执行效率低
from multiprocessing import Process
import time
x = 100
def task(x):
print('%s is begin'%x)
time.sleep(3)
print('%s is over'%x)
def main():
print('主:')
if __name__ == '__main__':
p1 = Process(target=task,args=('p',))
p2 = Process(target=task,args=('p2',))
p3 = Process(target=task,args=('p3',))
start_time = time.time()
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
print(time.time()-start_time)
main() #结果为:9.32131531515秒 (串行)
7.参数
- P1.name 起名字
- p1.start() 开启进程
- p1.terminate() 杀死进程
- p1.is_alive() True or false 判断进程的死活
- p1.join() 通知p,子程序结束后再执行主程序
- p1.pid 获取子程序
8.僵尸进程 与 孤儿进程
linux 系统:
你的进程空间在内存中真正的消失才算是进程结束.
1) 僵尸进程: 死而不腐, p1 p2 p3 在代码结束时都没有完全死掉,
他们会形成一个僵尸进程:僵尸进程只会保存pid,运行时间,状态.
2) 僵尸进程的回收,是由主进程发起的
3) 孤儿进程?
你的主进程意外挂了,剩下 p1 p2 p3 就形成 孤儿进程
所以你的孤儿都会交给'民政局'处理. init 是 Linux 所有进程的主进程.
4) 僵尸进程有害的.
僵尸进程他的回收取决于主进程,如果主进程产生了大量的子进程,但是不着急回收这些变成了的僵尸进程.
孤儿进程 无害 都会被民政局 init 回收.
Linux系统:具体回收方法: waitpid()
windows系统:
p1.join()源码中 有waitpid()方法
9.守护进程
子进程 守护 主进程
就一个参数 p1.daemon=True #你的p1进程就设置成了守护进程 主进程结束子进程也会跟着结束
10.进程锁
from multiprocessing import Process,Lock
import time
import json
def search_ticket(name):
# 查票
with open('ticket') as f:
ticket_dict = json.load(f)
time.sleep(0.1)
print('%s: 当前余票%s张'%(name,ticket_dict['count']))
def buy_ticket(name,lock):
# lock.acquire()
with open('ticket') as f:
ticket_dict = json.load(f)
time.sleep(0.1)
if ticket_dict['count']>0:
print('%s 查询余票%s'%(name,ticket_dict['count']))
print('%s 购票成功'%name)
ticket_dict['count'] -=1
with open('ticket','w') as f :
json.dump(ticket_dict,f)
# lock.release()
# def user_system(name,lock):
# search_ticket(name)
# lock.acquire()
# buy_ticket(name,lock)
# lock.release()
def user_system(name,lock):
search_ticket(name)
with lock:
buy_ticket(name,lock)
if __name__ == '__main__':
lock = Lock()
for i in range(5):
p=Process(target=user_system,args=('x'+str(i),lock))
p.start()
11.生产者 消费者
import time
import random
from multiprocessing import Process, Queue
def consumer(q, name):
while True:
task = q.get()
if task is None:
break
time.sleep(random.random())
print('%s吃了%s' % (name, task))
def produce(q, n):
for i in range(n):
time.sleep(random.uniform(1, 2))
print('生产了泔水%s' % i)
q.put('泔水%s' % i)
if __name__ == '__main__':
q = Queue()
pro_l = []
for i in range(3):
p = Process(target=produce, args=(q, 5))
p.start()
pro_l.append(p)
p1 = Process(target=consumer, args=(q, 'alex'))
p2 = Process(target=consumer, args=(q, 'wusir'))
p1.start()
p2.start()
for p in pro_l:
p.join()
q.put(None)
q.put(None)
12.进程队列
q=Queue() q.put() q.get()
q.empty() # 判断队列是否为空
q.full() # 判断队列是否为满
q .qsize() # 队列中数据的个数
q.get_nowait() (数据不会丢)
q.put_nowait()(数据容易丢)
进程池
apply_async(fun,args=())
一般的进程,线程还得写上target,args()
1.进程池的异步提交 apply提交任务,async异步
p.close()p.join()执行的是函数里边的结果
不写close join ,直接pring(ret.get())是执行return的结果
import time
import random
from multiprocessing import Pool
def foo(i):
time.sleep(random.random())
return 'i'*i
if __name__ == '__main__':
p = Pool()
ret_l = []
# ret = p.apply_async(foo, (i,))
for i in range(5):
ret =p.apply_async(foo,(i,))
ret_l.append(ret)
#print(ret.get())
# p.close()
# p.join() 所有的结果都回来了,再打印
for i in ret_l:
print(i.get()) 谁先回来先执行谁
使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
开启多进程来处理多个任务, 没有阻塞的话 ,那么起多进程效率反而降低
一个进程的开销
进程的开启
进程的销毁
进程的调度
map方法
map就是一种简便的apply_async的方式,并且内置了close和join的功能
map(fun,’必须是可迭代的’)
from multiprocessing import Pool
import time
def func(i,):
i*i
time.sleep(1)
return 'i'*i
if __name__ == '__main__':
p = Pool(4)
ret_l = p.map(func,range(5))
for ret in ret_l:
print(ret)
回调函数 callback
import os
from urllib import request
from multiprocessing import Pool
def aa(content):
print(os.getpid())
print('len' ,len(content))
def get_url(i):
ret=request.urlopen(i)
content=ret.read().decode('utf-8')
return content
if __name__ == '__main__':
print(os.getpid())
url_list = [
'http://www.cnblogs.com/Eva-J/articles/8253549.html', # 1
'http://www.cnblogs.com/Eva-J/articles/8306047.html', # 0.05
'http://www.baidu.com',
'http://www.sogou.com',
'https://www.cnblogs.com/Eva-J/p/7277026.html'
]
p=Pool()
# ret_l=[]
for i in range(5):
ret=p.apply_async(get_url,(i,),callback=aa)
# ret_l.append(ret)
# for i in ret_l:
p.close()
p.join()
# res=i.get()
# aa(res)
线程
参数
获取id: p.ident
获取名字: p.name
currentThread模块也可以获取id 名字
enumerate(): 当前所有活着的线程(开启的线程)对象组成的列表
Active_count(): 统计当前活着的线程对象的个数(len(enumerate()))
特点
数据共享 开销小 能利用多核(除了cpython解释器),是CPU调度的最小单位.,每一个进程中至少有一个线程
线的数据共享
from threading import Thread
n =100
def func():
global n
n -=1
t_l = []
for i in range(100):
t = Thread(target=func,)
t.start()
t_l.append(t)
for t in t_l:
t.join()
print(22,n)
守护线程 setdaemon(True)
<span style='color:blue'>守护进程 会等待主进程的代码结束而结束
守护线程 会等待主线程的结束而结束
如果主线程还开启了其他子线程,那么主线程死了,守护线程会守护到最后
主进程必须后结束,回收子进程的资源
线程是属于进程的,主线程如果结束了,那么整个进程就结束了</span>
GIL锁(全局解释器)
GIL 全局解释器锁
锁的是线程 让同一个进程中的多个线程同一时刻只能有一个线程被CPU调度
互斥锁
在同一个线程只能被acquire一次
递归锁;RLock
在一个线程内可以被acquire多次而不被锁住,(必须acquire多少次,release多少次 (科学家吃面))
死锁现象(用递归锁解决)
同一个线程中 出现了两把锁 同时锁两个资源去进行某些操作(使用操作不当)
Cpython解释器:高计算型 开多进程 高IO型 开多线程
线程队列
-
(IPC机制 所有队列都自带锁,安全) fifo 先进先出 lifo 先进后出
Import queue
q=queue.Queue()
q.put(1)
print(q.get())
import queue
q=queue.LifoQueue()
2)优先级队列 PriorityQueue
import queue
q=queue.PriorityQueue()
线程池concurrent.futures
用map(fun,可迭代)
concurrent.futures模块提供了高度封装的异步调用接口
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def foo(i):
time.sleep(1)
print('in foo',i)
# if __name__ == '__main__':
tp = ThreadPoolExecutor(4)
ss = tp.map(foo,range(100))
# for i in range(20):
# tp.submit(foo,i)
# tp.shutdown()
print('所有的都返回')
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
shutdown(wait=True) 相当于进程池的pool.close()+pool.join()操作
协成
特点
协程的本质是一条线程
1.不能利用多核
2.用户级的概念,操作系统不可见
3.协程不存在数据不安全问题
1)定义: 在一条线程中,能够在多个任务之间任意的切换。
其中的每一个任务都可以成为一个协程; 能够放多个任务在一个线程中
g=greenlet(eat) g.Switch() 切换
2)greenlet
from greenlet import greenlet
def eat():
print('start eating')
g1.switch()
print('eat finished')
def sleep():
print('start sleeping')
g.switch()
print('sleeping finished')
g = greenlet(eat)
g1 = greenlet(sleep)
g.switch()
g1.switch()
3)gevent
法一:用sleep
import gevent
def eat():
print('start eating')
gevent.sleep(1)
print('eat finished')
def sleep():
print('start sleeping')
gevent.sleep(1)
print('sleeping finished')
g = gevent.spawn(eat)
g1 = gevent.spawn(sleep)
gevent.sleep(1)
gevent.sleep(1)有了它才能执行函数代码,这个结果不切换
法二:用join
from gevent import monkey
monkey.patch_all()
import time
import gevent
def eat():
print('start eating')
time.sleep(1)
print('eat finished')
def sleep():
print('start sleeping')
time.sleep(1)
print('sleeping finished')
# g = gevent.spawn(eat)
# g1 = gevent.spawn(sleep)
# g.join()
# g1.join()
g_l = []
for i in range(5):
g = gevent.spawn(eat)
g1 = gevent.spawn(sleep)
g_l.append(g)
g_l.append(g1)
gevent.joinall(g_l)
4)两个协程模块
gevent - 基于greenlet - 使用更方便 性能相对低
asyncio - 基于yield - 性能更好
总结
并发阶段
进程 : 适合处理高计算型的应用场景
正在运行的程序
开启销毁调度开销大
可以利用多核
数据隔离(是计算机中最小的资源分配单位)
多进程 存在数据不安全(在共同操作文件数据库消息中间件(管道))
正常的线程: 线程是进程内的执行单位,适合处理程序中高并发(计算io)的问题
能被CPU调度的最小单位
数据共享
能利用多核,(除了cpython解释器)
开启销毁调度开销比进程小很多
多线程 存在数据不安全(在共同操作文件数据库全局变量)
Cpython中的线程
GIL全局解释器锁,同一个进程内的多个线程,同一时刻,只能有一个线程被CPU调度,不能利用多核
仍然数据不安全 仍然需要加锁
你知道哪些锁
互斥锁 : 在同一个线程中只能连续acquire一次,多线程时,保证修改共享数据时时有序修改,不会产生数据修改混乱
递归锁 : 在同一个线程中可以多次acquire而不被锁住(但是acquire多少次就要有对应的多少次release)
死锁现象
递归锁和互斥锁使用不当都有可能发生死锁现象
死锁现象发生的条件 总是因为在一个线程中有两把锁
队列
进程队列(IPC机制): 先进先出 数据安全 multiprocessing.Queue
通信,发送数据
线程队列 : queueu模块 维护顺序数据安全的
先进先出的队列 Queue
优先级队列 PriorityQueue
栈(后进先出) LifoQueue
解耦 : 把程序中的功能都分开 使得功能之间的依赖关系变得明确
生产者消费者模型
生产数据 和消费数据 两个功能的解耦
能够由用户调节生产者 消费者的数据 使得程序的效率达到最高
维护了数据的顺序
在数据过剩的情况下,能够先将数据缓存下来,而不是堆在网络入口的地方让用户的信息过长时间的等待
池 concurrent.futrues
进程池 ProcessPoolExcutor 无论有多少个任务 进程的个数都是有限的
推荐的进程数 (cpu个数的1-2倍)
线程池 ThreadPoolExcutor 无论有多少个任务 线程的个数都是有限的
协程
在一条线程上 能够在多个任务之间进行切换
gevent模块 实现的协程
开多少个协程 和程序中的IO操作有关系
- 为什么要有GIL锁?
因为CPython自带的垃圾回收机制,在执行多线程的时候并不是线程安全的,所以加了GIL锁
如果没有GIL 锁,则Python解释器的垃圾回收线程和任务线程就可以并行,在任务线程I/O时,某些变量值的引用技术为0,很可能会被回收,导致数据不安全
同步与异步
比如说你要去一个餐厅吃饭,你点完菜以后假设服务员告诉你,你点的菜,要两个小时才能做完,这个时候你可以有两个选择
•一直在餐厅等着饭菜上桌
•你可以回家等着,这个时候你就可以把你的电话留给服务员,告诉服务员等什么时候你的饭菜上桌了,在给你打电话
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。
阻塞与非阻塞
继续上面的例子
•不管你的在餐厅等着还是回家等着,这个期间你的都不能干别的事,那么该机制就是阻塞的,表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行。
•你回家以后就可以去做别的事了,一遍做别的事,一般去等待服务员的电话,这样的状态就是非阻塞的,因为你(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待。
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的
同步/异步与阻塞/非阻塞
同步阻塞形式
效率最低。拿上面的例子来说,就是你专心的在餐馆等着,什么别的事都不做。
异步阻塞形式
在家里等待的过程中,你一直盯着手机,不去做其它的事情,那么很显然,你被阻塞在了这个等待的操作上面;
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
同步非阻塞形式
实际上是效率低下的。
想象一下你如果害怕服务员忘记给你打电话通知你,你过一会就要去餐厅看一下你的饭菜好了没有,没好 ,在回家等待,过一会再去看一眼,没好再回家等着,那么效率可想而知是低下的。
异步非阻塞形式
比如说你回家以后就直接看电视了,把手机放在一边,等什么时候电话响了,你在去接电话.这就是异步非阻塞形式,大家想一下这样是不是效率是最高的
那么同步一定是阻塞的吗?异步一定是非阻塞的吗?
生产者消费者模型
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过消息队列(缓冲区)来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给消息队列,消费者不找生产者要数据,而是直接从消息队列里取,消息队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个消息队列就是用来给生产者和消费者解耦的。------------->这里又有一个问题,什么叫做解耦?
解耦:假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。