第九章.并发编程
第九章 并发编程
9.1操作系统简介
9.1.1手工操作 -穿孔卡片
1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式。此时还没有操作系统的概念。
程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。
-
手工操作的两个特点:
-
用户独占全机。不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低。
-
CPU 等待手工操作。CPU的利用不充分。
-
-
人机矛盾
9.1.2 批处理 - 磁带存储
-
批处理系统
加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理一个或多个用户的作业(包括程序、数据和命令)。
-
特点 : 提高cpu的利用率 , 降低数据的读取时间
-
不足
每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低速的I/O完成状态,致使CPU空闲。
为改善CPU的利用率,又引入了多道程序系统。
9.1.3多道程序系统
-
什么是多道程序系统
指同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。
-
单道程序技术
在A程序计算时,I/O空闲, A程序I/O操作时,CPU空闲(B程序也是同样);必须A工作完成后,B才能进入内存中开始工作,两者是串行的,全部完成共需时间=T1+T2。
-
多道程序技术
将A、B两道程序同时存放在内存中,它们在系统的控制下,可相互穿插、交替地在CPU上运行:当A程序因请求I/O操作而放弃CPU时,B程序就可占用CPU运行,这样 CPU不再空闲,而正进行A I/O操作的I/O设备也不空闲,显然,CPU和I/O设备都处于“忙”状态,大大提高了资源的利用率,从而也提高了系统的效率,A、B全部完成所需时间<<T1+T2。
-
能够在一个任务遇到io操作的时候主动把cpu让出来,给其他的任务使用 ,切换由操作系统完成 ,切换需要占用时间
多道程序设计技术不仅使CPU得到充分利用,同时改善I/O设备和内存的利用率,从而提高了整个系统的资源利用率和系统吞吐量(单位时间内处理作业(程序)的个数),最终提高了整个系统的效率。
9.1.4分时系统
-
把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用。若某个作业在分配给它的时间片内不能完成其计算,则该作业暂时中断,把处理机让给另一作业使用,等待下一轮时再继续其运行。由于计算机速度很快,作业运行轮转得很快,给每个用户的印象是,好象他独占了一台计算机。而每个用户可以通过自己的终端向系统发出各种操作控制命令,在充分的人机交互情况下,完成作业的运行。
-
注意 :分时系统的分时间片工作,在没有遇到IO操作的时候就用完了自己的时间片被切走了(切换需要占用时间),这样的切换工作其实降低了cpu的利用率。但是我们牺牲了一点效率,却实现了多个程序共同执行的效果,提高了用户体验
9.1.5 实时系统
-
系统能够及时响应随机发生的外部事件,并在严格的时间范围内完成对该事件的处理。
-
特点 :及时响应 ,高可靠性
注 : 分时——现在流行的PC,服务器都是采用这种运行模式,即把CPU的运行分成若干时间片分别处理不同的运算请求 linux系统 实时——一般用于单片机上、PLC等,比如电梯的上下控制中,对于按键等动作要求进行实时处理
9.1.6通用操作系统
-
具有多种类型操作特征的操作系统。可以同时兼有多道批处理、分时、实时处理的功能,或其中两种以上的功能。
-
多个程序一起在计算机中执行 ,一个程序如果遇到IO操作,切出去让出CPU,如果没有遇到IO,但是时间片到时了,也会切出去让出CPU
9.2 进程
9.2.1进程的基本概念
-
进程是计算机中的最小资源分配单位,是运行中的程序
-
特点 :数据隔离,创建进程时间开销大 ,销毁进程时间开销大 ,进程之间切换时间开销大
-
在目前完成的一些项目不常用
-
在操作系统中的唯一标识符 :pid
-
注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。
#程序和进程的区别?
程序只是一个文件,进程是这个文件被CPU运行起来了
#为什么进程用的不多,还是要讲?
1.是基本功
2.可能用于非常复杂的数据分析或高级算的程序
3.进程和线程的很多模型很多概念基本一致
# 操作系统引入进程的概念的原因
1.从理论角度看,是对正在运行的程序过程的抽象;
2.从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
9.2.2进程的调度(几个重要算法)
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。
短作业优先算法
先来先服务算法
时间片轮转算法
多级反馈算法
只要第一季队列里有任务,就不会执行第二级队列
9.2.3并行与并发
-
并行
两个程序 两个CPU 每个程序分别占用一个CPU自己执行自己的
看起来是同时执行,实际在每一个时间点上都在各自执行着
-
并发
两个程序 一个cpu 每个程序交替的在一个cpu上执行或者在一个时间点上看,多个程序同时在多个cpu上同时运行
看起来在同时执行,但是实际上仍然是串行
9.2.4 同步,异步,阻塞,非阻塞
-
同步
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,两个任务的状态可以保持一致。调用一个方法要等待这个方法结束
-
异步
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了
。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,
所以它是不可靠的任务序列`。调用一个方法 不等待这个方法结束 也不关心这个方法做了什么 -
阻塞
cpu不工作
-
非阻塞
cpu工作
-
同步阻塞
#例 :conn.recv
socket阻塞的tcp协议 -
同步非阻塞
#自定义一个func函数,但没有I/O操作
socket非阻塞的tcp协议 -
异步非阻塞
#把func函数放到其他任务中执行,但没有I/O操作
#本身任务和func任务各自执行自己的
terminate是一个异步非阻塞 -
异步阻塞
9.2.5进程的三状态图
9.2.6 multiprocessing模块
-
mulprocessing中有一个manager类,封装了所有和进程相关的 数据共享 数据传递,但是对于 字典 列表这一类的数据操作的时候会产生数据不安全,需要加锁解决问题,并且需要尽量少的使用这种方式
-
multi muitiple多元的 processing进程
import os
import time
print('start')
time.seep(10)
print(os.getpid(),os.getppid())
#pid process id 进程id
#ppid parent process id 父进程id# 子进程和父进程
注:在pycharm中启动的所有py程序都是pycharm的子进程进程的开启 :
import os
import time
from multiprocessing import Process
def func():
print('start',os.getpid())
time.sleep(1)
print('end',os.getpid())
if __name__ == '__main__':
p = Process(target=func) #实例化一个对象
p.start() # 异步非阻塞 调用开启进程的方法,但是并不等待这个进程真的开启,开启线程几乎不需要花费时间
print('main :',os.getpid())
#main : 12760
#start 14840
#end 14840#有延迟,print提前
import os
import time
from multiprocessing import Process
def eat():
print('start eating',os.getpid())
time.sleep(1)
print('end eating',os.getpid())
def sleep():
print('start sleeping',os.getpid())
time.sleep(1)
print('end sleeping',os.getpid())
if __name__ == '__main__':
p1 = Process(target=eat) #创建一个即将执行eat函数的进程对象
p1.start() #开启进程
p2 = Process(target=sleep) #创建一个即将执行sleep函数的进程对象
p2.start() #开启进程
print('main :',os.getpid())#有延迟,print不提前
import os
import time
from multiprocessing import Process
def eat():
print('start eating',os.getpid())
time.sleep(1)
print('end eating',os.getpid())
def sleep():
print('start sleeping',os.getpid())
time.sleep(1)
print('end sleeping',os.getpid())
if __name__ == '__main__':
p1 = Process(target=eat) #创建一个即将执行eat函数的进程对象
p1.start() #开启进程
p2 = Process(target=sleep) #创建一个即将执行sleep函数的进程对象
p2.start() #开启进程
print('main :',os.getpid())#无延迟,print提前
import os
import time
from multiprocessing import Process
def eat():
print('start eating',os.getpid())
time.sleep(1)
print('end eating',os.getpid())
def sleep():
print('start sleeping',os.getpid())
time.sleep(1)
print('end sleeping',os.getpid())
if __name__ == '__main__':
p1 = Process(target=eat) #创建一个即将执行eat函数的进程对象
p1.start() #开启进程
p2 = Process(target=sleep) #创建一个即将执行sleep函数的进程对象
p2.start() #开启进程
print('main :',os.getpid())#无延迟,print不提前
import os
import time
from multiprocessing import Process
def eat():
print('start eating',os.getpid())
time.sleep(1)
print('end eating',os.getpid())
def sleep():
print('start sleeping',os.getpid())
time.sleep(1)
print('end sleeping',os.getpid())
if __name__ == '__main__':
p1 = Process(target=eat) #创建一个即将执行eat函数的进程对象
p1.start() #开启进程
p2 = Process(target=sleep) #创建一个即将执行sleep函数的进程对象
p2.start() #开启进程
print('main :',os.getpid()) -
关于name == 'main'的一个错误
windows操作系统
主进程中的func和子进程中的func指向的内存地址不同
在执行完最后一句print('main:',os.getpid())时
-
主进程还没有结束 :而是等待着子进程结束.
-
主进程负责回收子进程的资源
-
如果子进程执行结束,父进程没有回收资源,那么这个子进程会变成一个僵尸进程
-
-
windows操作系统执行开启进程的代码(import执行代码导入方式)
-
ios/linux操作系统执行开启进程的代码(copy方式)
-
子进程和主进程相互隔离
-
主进程如何知道子进程结束?
基于网络或文件
-
开启10个进程,给公司5000个人发邮件,发送完邮件之后,打印一个消息'5000'封邮件已发送.
from muitiprocessing import Process
def send_mail():
print('发送了一封邮件')
if __name__ == '__main__':
for i in range(10):
p =Process(target=send_mail)
p.start()
#少了一个阻塞,直到上面的10封邮件已发送
print('5000封邮件已发送完毕') -
join方法 :阻塞,直到子进程结束就结束
from muitiprocessing import Process
def send_mail():
print('发送了一封邮件')
if __name__ == '__main__':
p =Process(target=send_mail)
p.start() #异步非阻塞
p.join() #同步阻塞,直到p对应的进程结束才结束阻塞
print('5000封邮件已发送完毕')import time
import random
from muitiprocessing import Process
def send_mail(a):
time.sleep(random.random())
print('发送了一封邮件')
if __name__ == '__main__':
l = []
for i in range(10):
p =Process(target=send_mail,args =(i,))
p.start()
l.append(p)
for p in l:
p.join()
print('5000封邮件已发送完毕')
9.2.7 守护进程
-
daemon参数可以把子进程设置成一个守护进程
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.daemon = True
p.start()
#跑完之后什么都没有import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
#p.daemon = True
p.start()
time.sleep(2)
#会一直打印'is alive'
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.daemon = True
p.start() #把p子程序设置成了一个守护进程
time.sleep(2)
#打印四个'is alive'
总结 : 子进程随着主进程的代码结束而结束,所有的子进程都必须在主进程结束前结束,由主进程负责回收资源
Qunun下的生产者消费者模型
-
什么是消费者生产者模型?
把一个产生数据并且处理数据的过程解耦,然后让生产数据的过程和处理数据的过程达到一个工作效率上的平衡,对于中间的容器,在多进程中我们使用队列或者可被join的队列,做到控制数据的量
当数据过剩的时候,队列的大小会控制这生产者的行为;当数据严重不足的时候,队列会控制消费者的行为
-
解耦
-
概念 : 把写在一起的大的功能分成多个小的功能处理
-
原因 : 方便修改,增加可读性
-
-
进程和生产者消费者模型关系
-
一个进程就是一个生产者
-
一个进程就是一个消费者
-
-
队列
-
生产者和消费者之间的容器就是队列
-
基于队列生产的消费者模型
import time,os
import random
from multiprocessing import Process,Queue
def consumer(q): #消费者
while True:
res = q.get()
time.sleep(random.randint(1, 3))
print('\033[45m%s 吃 %s\033[0m' % (os.getpid(), res))
def producer(q): #生产者
for i in range(10):
time.sleep(random.randint(1, 3))
res = '包子%s' % i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' % (os.getpid(), res))
if __name__ == '__main__':
q = Queue()
# 生产者们:即厨师们
p1 = Process(target=producer, args=(q,))
import time,os
import random
from multiprocessing import Process,Queue
def consumer(q): #消费者
while True:
res = q.get()
time.sleep(random.randint(1, 3))
print('\033[45m%s 吃 %s\033[0m' % (os.getpid(), res))
def producer(q): #生产者
for i in range(10):
time.sleep(random.randint(1, 3))
res = '包子%s' % i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' % (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('主')
我们发现,此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。那么如何解决呢?
解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break #收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(q):
for i in range(10):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
q.put(None) #发送结束信号
if __name__ == '__main__':
q=Queue()
#生产者们:即厨师们
p1=Process(target=producer,args=(q,))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
#开始
p1.start()
c1.start()
print('主')
注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号
主进程在生产者生产完毕后发送结束信号None
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break #收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(q):
for i in range(2):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
#生产者们:即厨师们
p1=Process(target=producer,args=(q,))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
#开始
p1.start()
c1.start()
p1.join()
q.put(None) #发送结束信号
print('主')
但上述解决方式,在有多个生产者和多个消费者时,我们则需要用一个很low的方式去解决
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break #收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(name,q):
for i in range(2):
time.sleep(random.randint(1,3))
res='%s%s' %(name,i)
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
#生产者们:即厨师们
p1=Process(target=producer,args=('包子',q))
p2=Process(target=producer,args=('骨头',q))
p3=Process(target=producer,args=('泔水',q))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
#开始
p1.start()
p2.start()
p3.start()
c1.start()
p1.join() #必须保证生产者全部生产完毕,才应该发送结束信号
p2.join()
p3.join()
q.put(None) #有几个消费者就应该发送几次结束信号None
q.put(None) #发送结束信号
print('主')
生产者消费者实现爬虫的例子
from multiprocessing import Process,Queue
import requests
import re
import json
def producer(q,url):
response = requests.get(url)
q.put(response.text)
def consumer(q):
while True:
s = q.get()
if not s:break
com = re.compile(
'<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
'.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S)
ret = com.finditer(s)
for i in ret:
print({
"id": i.group("id"),
"title": i.group("title"),
"rating_num": i.group("rating_num"),
"comment_num": i.group("comment_num")}
)
if __name__ == '__main__':
count = 0
q = Queue(3)
p_l = []
for i in range(10):
url = 'https://movie.douban.com/top250?start=%s&filter='%count
count+=25
p = Process(target=producer,args=(q,url,)).start()
p_l.append(p)
p = Process(target=consumer, args=(q,)).start()
for p in p_l:p.join()
q.put(None)
JoinableQueue([maxsize])
JoinableQueue队列实现消费之生产者模型
from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
while True:
res=q.get()
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了
def producer(name,q):
for i in range(10):
time.sleep(random.randint(1,3))
res='%s%s' %(name,i)
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
q.join() #生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。
if __name__ == '__main__':
q=JoinableQueue()
#生产者们:即厨师们
p1=Process(target=producer,args=('包子',q))
p2=Process(target=producer,args=('骨头',q))
p3=Process(target=producer,args=('泔水',q))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
c1.daemon=True
c2.daemon=True
#开始
p_l=[p1,p2,p3,c1,c2]
for p in p_l:
p.start()
p1.join()
p2.join()
p3.join()
print('主')
#主进程等--->p1,p2,p3等---->c1,c2
#p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
#因而c1,c2也没有存在的价值了,不需要继续阻塞在进程中影响主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了。
和守护线程作对比的时候及生产者消费者模型
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
def son2():
for i in range(5):
print('in son2')
time.sleep(1)
if __name__ == '__main__':
p = Process(target=son1)
p.daemon = True
p.start() #把p子程序设置成了一个守护进程
p2 = Process(target=son2)
P2.start()
time.sleep(2)
#将p设置成守护进程,当主进程结束后,p结束
9.2.8 process类
-
is_alive() 判断一个程序是不是还在运行
-
x.terminate() 不管任务是否完成,立即停止工作进程
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.start()
print(p.is_alive())
time.sleep(1)
p.terminate()
print(p.is_alive())
print(p.is_alive())
print(p.is_alive())
print(p.is_alive())
print(p.is_alive())
#结果见下图import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.start() #异步非阻塞
print(p.is_alive())
time.sleep(1)
p.terminate() #异步非阻塞
print(p.is_alive()) #进程还活着,因为操作系统还没有来得及关闭程序
time.sleep(0.01)
print(p.is_alive()) #操作系统已响应我们要关闭程序的需求,再去检测的时候,得到结果是是进程已结束
总结:提供两种开启进程的方式
-
面向函数
def 函数名():
要在子进程中执行代码
p = Process(target=函数名,args=(参数一,))
p.start()开启进程 -
面向对象
class 类名(Process):
def __init__(self,参数一,参数二):
self.a = 参数一
self.b = 参数二
super().__init__()
def run(self):
要在子进程中执行的代码
p = 类名(参数一,参数二)
p.start()开启进程 #异步非阻塞
p.terminate()结束进程 #异步非阻塞
p.join() #同步阻塞
9.2.9锁
#抢票系统
import json,time
from multiprocessing import Process
def search_ticket(user): #查票
with open('ticket_count')as f:
dict = json.load(f)
print('%s查询结果:%s张余票' %(user,dict['count']))
if __name__ == '__main__':
for i in range(10):
p = Process(target=search_ticket,args=('user%s' %i,))
p.start()
import json,time
from multiprocessing import Process
def search_ticket(user): #查票
with open('ticket_count')as f:
dict = json.load(f)
print('%s查询结果:%s张余票' %(user,dict['count']))
def buy_ticket(user): #买票
time.sleep(0.02)
with open('ticket_count')as f:
dict = json.load(f)
if dict['count'] > 0:
print('%s买到票了' %(user,))
dict['count'] -= 1
else:
print('%s没买到票' %user)
time.sleep(0.02)
with open('ticket_count','w')as f:
json.dump(dict,f)
if __name__ == '__main__':
for i in range(10):
p = Process(target=buy_ticket,args=('user%s' %i,))
p.start()
-
但只有两张票
-
解决方法:加锁
import json,time
from multiprocessing import Process,Lock
def search_ticket(user): #查票
with open('ticket_count')as f:
dict = json.load(f)
print('%s查询结果:%s张余票' %(user,dict['count']))
def buy_ticket(user,lock): #买票
with lock:
# lock.acquire() #给这段代码加上锁
time.sleep(0.02)
with open('ticket_count')as f:
dict = json.load(f)
if dict['count'] > 0:
print('%s买到票了' %(user,))
dict['count'] -= 1
else:
print('%s没买到票' %user)
time.sleep(0.02)
with open('ticket_count','w')as f:
json.dump(dict,f)
# lock.release() #给这段代码解锁
if __name__ == '__main__':
lock = Lock()
for i in range(10):
p = Process(target=buy_ticket,args=('user%s' %i,lock))
p.start()#1.如果在一个并发的场景下,涉及到某部分内容
1.需要修改一些所有的进程共享的数据资源
2.需要加锁来维护数据的安全
#2.在数据安全的基础上,才考虑效率问题
#3.同步存在的意义就是数据的安全性
9.2.10进程之间的数据隔离
from multiprocessing import Process
n = 100
def func():
global n
n -= 1
if __name__ == '__main__':
p_l = []
for i in range(10):
p = Process(target=func)
p.start()
p_l.append(p)
for p in p_l:p.join()
print(n) #不能直接写p.start()原因,是p.start()和print(n) 是异步的,不能保证p意境读完再打印n
9.2.11 进程之间的数据共享
mulprocessing中有一个manager类 ,封装了所有和进程相关的数据共享,数据传递相关的数据类型 但是对于 字典 列表这一类的数据操作的时候会产生数据不安全,需要加锁解决问题,并且需要尽量少的使用这种方式
from multiprocessing import Manager,Process,Lock
def func(dic,lock):
with lock:
dic['count'] -= 1
if __name__ == '__main__':
# m = Manager()
with Manager() as m:
l = Lock()
dic = m.dict({'count':100})
p_l = []
for i in range(100):
p = Process(target=func,args=(dic,l))
p.start()
p_l.append(p)
for p in p_l:p.join()
print(dic)
9.2.12IPC
进程之间的通信 --- IPC(inter process communication)
-
Queue (队列) :先进先出 ,天生数据安全
-
基于文件级别的socket,pickle,lock
-
from multiprocessing import Queue, Process
def func(exp,q):
ret = eval(exp)
q.put(ret)
q.put(ret * 2)
q.put(ret * 4)
if __name__ == '__main__':
q = Queue()
Process(target=func, args=('1+2+3', q)).start()
print(q.get())
print(q.get())
print(q.get())
#6
#12
#24 先进先出
import queue
from multiprocessing import Queue
q = Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5) # 当队列为满的时候再向队列中放数据 队列会阻塞
print('5555555')
try:
q.put_nowait(6) # 当队列为满的时候再向队列中放数据 会报错并且会丢失数据,处理方法,导入queue模块(不常用)
except queue.Full:
pass
print('6666666')
print(q.get())
print(q.get())
print(q.get()) # 在队列为空的时候会发生阻塞
print(q.get()) # 在队列为空的时候会发生阻塞
print(q.get()) # 在队列为空的时候会发生阻塞
try:
print(q.get_nowait()) # 在队列为空的时候 直接报错(常用)
except queue.Empty:pass
-
pipe 管道天生数据不安全
-
基于文件级别的socket,pickle,无锁
-
用于只有一收一发
from multiprocessing import Pipe
pip = Pipe()
pip.send()
pip.recv() -
注 :
不使用,原因所得结果不准
9.3线程
9.3.1线程的基本概念
-
能被cpu调度的最小单位
-
特点 :开销小,数据共享 是进程的一部分,不能脱离线程独立存在 本身可以利用多核(同时执行几段代码)每个进程最少有一个线程,线程负责执行代码,一个进程中的多个线程可以共享这个进程中的数据 ,由操作系统调度
-
后面爬虫常用,前段障碍
-
开销
-
线程的创建,也需要一些开销(一个存储局部(临时)变量的结构,记录状态(切换时))
-
创建,销毁,切换开销过程远小于进程
-
python中线程比较特殊,所以进程也可能被用到
-
-
主线程等待所有子线程结束之后才结束,主线程如果结束了,主进程也就结束了
-
数据隔离还是数据共享
from threading import Thread
n = 100
def func():
global n # 不要在子线程里随便修改全局变量
n-=1
t_l = []
for i in range(100):
t = Thread(target=func)
t_l.append(t)
t.start()
for t in t_l:t.join()
print(n)
9.3.2 cpython解释器的相关知识
9.3.2.1Python程序的执行过程
cpython分两步来运行python程序
-
第一步:把文本形式的源代码解析并编译成字节码;
-
第二步:用一种基于栈的解释器来运行第一步产生的字节码。
9.3.2.2确保Cpython解释器状态保持一致的方法
-
Python采用GIL(global interpreter lock)机制来确保一致性,以防止Cpython受到抢占式多线程切换操作的干扰。Python程序尽管也支持多线程,但由于受到GIL保护,所以同一时刻,只能有一个线程得到执行。这就意味着,多条python线程不能在多个CPU核心上面平行地执行字节码。
-
原因:cpython解释器中特殊的垃圾回收机制
-
GIL锁导致了线程可以并发,不能并行
-
所以使用所线程并不影响高io型的操,作只会对高计算型的程序由效率上的影响
-
遇到高计算 :可采用 解决
-
多进程 + 多线程
-
分布式
-
9.3.2.3 GIL
-
什么是gil
-
全局解释器锁,由cpython解释器提供,导致了一个进程中的多个线程在同一时刻只能有一个线程访问cpu
-
我们先看一下官方对GIL的解释
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
简单的一句话包含了很多信息;
a)在Python众多解释器中,只有Cpython才有GIL,JPython就没有;因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL
归结为Python语言的缺陷。明确一点,GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,Python完全可以不依赖于GIL
9.3.3 threading模块
-
threading模块的函数 :
-
current_thread 在哪个线程中被调用,就返回当前线程的对象
-
enumerate 返回当前活着的线程的对象列表
-
active_count 返回当前活者的线程的个数(活着的线程,包括主线程)
-
#开启单个线程
import os
import time
from threading import Thread
def func():
print('start son thread')
time.sleep(1)
print('end son thread',os.getpid())
p = Thread(target=func)
p.start() #启动线程
print('start',os.getpid())
time.sleep(0.5)
print('end',os.getpid())
#开启多个线程
import os
import time
from threading import Thread
def func(i):
print('start son thread',i)
time.sleep(1)
print('end son thread',i,os.getpid())
for i in range(3):
Thread(target=func,args=(i,)).start() #异步非阻塞
print('main')
join方法
阻塞,直到子线程执行结束
import os
import time
from threading import Thread
def func(i):
print('start son thread',i)
time.sleep(1)
print('end son thread')
t_l = []
for i in range(10):
t = Thread(target=func,args=(i,))
t.start()
t_l.append(t)
for t in t_l:
t.join()
print('子线程执行完毕')
9.3.4使用面向对象的方式启动线程
import os
import time
from threading import Thread
class MyThread(Thread):
def __init__(self,i):
self.i = i
super().__init__()
def run(self):
print('start',self.i,self.ident) #ident 线程ID
time.sleep(1)
print('end',self.i)
for i in range(5):
t = MyThread(i)
t.start()
print(t.ident)
线程里的一些其他方法
import os
import time
from threading import Thread
from threading import current_thread,enumerate,active_count
def func(i):
t = current_thread() #当前线程
print('start son thread',i,t.ident)
time.sleep(1)
print('end son thread',i,os.getpid())
t = Thread(target=func,args=(i,))
t.start()
print(t.ident)
print(current_thread().ident) #主线程线程ID,水性杨花 在哪一个线程里,current_thread()得到的就是这个当前线程的信息
print(enumerate()) #查看当前有多少线程活着
print(active_count()) #=== len(enumerate())活着线程的个数
#terminate 结束进程,但在线程中不能从主线程结束子线程
9.3.5线程和进程的效率差
import time
from threading import Thread
from muitiprocessing import Process
def func(a,b):
c = a+b
if __name__ == '__main__':
start = time.time()
P_L = []
for i in range(100):
p = Process(target=func,args=(i,i*2))
p.start()
P_l.append(p)
for p in p_l:
p.join()
print('process',time.time()-start)
start = time.time()
P_L = []
for i in range(100):
p = Thread(target=func,args=(i,i*2))
p.start()
P_l.append(p)
for p in p_l:
p.join()
print('thread',time.time()-start)
9.3.6 守护线程
守护线程一直等到所有非守护线程结束才结束,除了守护主线程的代码,也会守护子线程
-
非守护线程不结束,主线程也不结束
-
主线程结束了,主进程也结束
-
结束顺序 : 非守护线程结束 -->主线程结束-->主进程结束(-->主进程结束 --> 守护线程也结束)
import time
from threading import Thread
def son1():
while True:
time.sleep(0.5)
print('in son1')
def son2():
for i in range(5):
time.sleep(1)
print('in son2')
t =Thread(target=son1)
t.daemon = True
t.start()
Thread(target=son2).start()
time.sleep(3)
9.3.7锁
-
线程中的数据不安全现象
线程的内存共享
from Threading import thread
a = 0
def add_f():
global a
for i in range(20000):
a += 1
def sub_f():
global a
for i in range(20000):
a -= 1
t1 = Thread(target=add_f)
t1.start()
t2 = Thread(target=sub_f)
t2.start()
t1.join()
t2.join()
#每次得到结果不同,不一定为0 -
dis :dis库是python(默认的CPython)自带的一个库,可以用来分析字节码(cpu指令)
import dis
a = 0
def func():
global a
dis.dis(func) -
即便是线程 即便有GIL 也会出现数据不安全的问题
-
1.操作的是全局变量
-
2.+= -= *= /= 先计算再赋值才容易出现数据不安全的问题, 包括 lst[0] += 1 dic['key']-=1
-
-
加锁会影响程序的执行效率,但是保证了数据的安全
from threading import Thread,Lock
a = 0
def add_f(lock):
global a
for i in range(200000):
with lock:
a += 1
def sub_f(lock):
global a
for i in range(200000):
with lock:
a -= 1
lock = Lock()
t1 = Thread(target=add_f,args=(lock,))
t2 = Thread(target=sub_f,args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
print(a) #0
互斥锁
在一个线程中连续多次acquire会死锁
from threading import Lock
lock = Lock()
lock.acquire()
print('*'*20)
lock.release()
lock.acquire()
print('-'*20)
lock.release()
with lock:
print('-'*20)
死锁现象
例 :科学家吃面问题
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name,noodle_lock,fork_lock):
noodle_lock.acquire()
print('%s抢到面了' %name)
fork_lock.acquire()
print('%s抢到叉子了' %name)
print('%s吃了一口面' %name)
time.sleep(0.1)
fork_lock.release()
print('%s放下叉子了' % name)
noodle_lock.release()
print('%s放下面了' % name)
def eat2(name,noodle_lock,fork_lock):
fork_lock.acquire()
print('%s抢到叉子了' % name)
noodle_lock.acquire()
print('%s抢到面了'%name)
print('%s吃了一口面'%name)
time.sleep(0.1)
noodle_lock.release()
print('%s放下面了' % name)
fork_lock.release()
print('%s放下叉子了' % name)
lst = ['alex','wusir','taibai','yuan']
Thread(target=eat1,args=(lst[0],noodle_lock,fork_lock)).start()
Thread(target=eat2,args=(lst[1],noodle_lock,fork_lock)).start()
Thread(target=eat1,args=(lst[2],noodle_lock,fork_lock)).start()
Thread(target=eat2,args=(lst[3],noodle_lock,fork_lock)).start()
-
死锁现象的发生
-
1.有多把锁.一把以上
-
2.多把锁交替使用
-
-
死锁现象的解决
递归锁
-
在同一个线程中,可以连续acquire多次不会被锁住
-
优点 : 在同一个进程中多次acquire也不会阻塞
-
缺点 : 占用了更多资源
from threading import RLock
rlock = RLock()
rlock.acquire()
print('*'*20)
rlock.acquire()
print('-'*20)
rlock.acquire()
print('*'*20)科学家吃面问题的解决
-
法一:递归锁
-
快速解决问题
-
效率差
-
-
注 :递归锁也可能产生死锁现象(多把递归锁交替使用时)
-
法二:优化代码逻辑
-
可以使用互斥锁解决
-
效率相对好,但自己解决问题速度相对低
-
import time
from threading import RLock,Thread
# noodle_lock = RLock()
# fork_lock = RLock()
noodle_lock = fork_lock = RLock()
print(noodle_lock,fork_lock)
def eat1(name,noodle_lock,fork_lock):
noodle_lock.acquire()
print('%s抢到面了'%name)
fork_lock.acquire()
print('%s抢到叉子了' % name)
print('%s吃了一口面'%name)
time.sleep(0.1)
fork_lock.release()
print('%s放下叉子了' % name)
noodle_lock.release()
print('%s放下面了' % name)
def eat2(name,noodle_lock,fork_lock):
fork_lock.acquire()
print('%s抢到叉子了' % name)
noodle_lock.acquire()
print('%s抢到面了'%name)
print('%s吃了一口面'%name)
time.sleep(0.1)
noodle_lock.release()
print('%s放下面了' % name)
fork_lock.release()
print('%s放下叉子了' % name)
lst = ['alex','wusir','taibai','yuan']
Thread(target=eat1,args=(lst[0],noodle_lock,fork_lock)).start()
Thread(target=eat2,args=(lst[1],noodle_lock,fork_lock)).start()
Thread(target=eat1,args=(lst[2],noodle_lock,fork_lock)).start()
Thread(target=eat2,args=(lst[3],noodle_lock,fork_lock)).start() -
9.3.8 线程的单例模式
import time
from threading import Lock
class A:
__instance = None
lock = Lock()
def __new__(cls,*args,**kwargs):
with cls.lock:
if not cls._instance:
cls.__instance = super().__new__()
return cls.__instance
def __init__(self,name,age):
self.name = name
self.age = age
a1 = A('alex',84)
a2 = A('wusir',73)
改进
import time
from threading import Lock
class A:
__instance = None
lock = Lock()
def __new__(cls, *args, **kwargs):
with cls.lock:
if not cls.__instance:
time.sleep(0.1)
cls.__instance = super().__new__(cls)
return cls.__instance
def __init__(self,name,age):
self.name = name
self.age = age
def func():
a = A('alex', 84)
print(a)
from threading import Thread
for i in range(10):
t = Thread(target=func)
t.start()
递归锁也可能产生死锁现象
9.3.9 队列
queue 不能用于进程
from queue import Queue # 先进先出队列
q = Queue(5)
q.put(0)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print('444444')
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
作业
# 使用多线程 实现一个请求网页 并且把网页写到文件中
# 生产者消费者模型来完成
from queue import LifoQueue # 后进先出队列
# last in first out 栈
lfq = LifoQueue(4)
lfq.put(1)
lfq.put(3)
lfq.put(2)
print(lfq.get())
print(lfq.get())
print(lfq.get())
#2
#3
#1
# 先进先出
# 写一个server,所有的用户的请求放在队列里
# 先来先服务的思想
# 后进先出
# 算法
# 优先级队列
# 自动的排序
# 抢票的用户级别 100000 100001
# 告警级别
from queue import PriorityQueue
pq = PriorityQueue()
pq.put((10,'alex'))
pq.put((6,'wusir'))
pq.put((20,'yuan'))
print(pq.get())
print(pq.get())
print(pq.get())
9.4池
-
概念 : 预先开启固定个数的进程数,当任务来临的时候,直接交给已经开好的进程,让这个进程去执行就可以了.他减少了进程,线程开启,关闭,切换的时间,并且减轻了操作系统的负担.
-
为什么要有池?
进程,线程的开启,关闭,切换都需要时间
concurrent.furtures模块
import os
import time
import random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
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() # 关闭池之后就不能继续提交任务,并且会阻塞,直到已经提交的任务完成
print('main',os.getpid())
import os
import time
import random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
# 任务的参数 + 返回值
def func(i,name):
print('start',os.getpid())
time.sleep(random.randint(1,3))
print('end', os.getpid())
return '%s * %s'%(i,os.getpid())
if __name__ == '__main__':
p = ProcessPoolExecutor(5)
ret_l = []
for i in range(10):
ret = p.submit(func,i,'alex')
ret_l.append(ret)
for ret in ret_l:
print('ret-->',ret.result()) # ret.result() 同步阻塞
print('main',os.getpid())
-
进程池
缺点 ; 一个池中的任务个数限制了我们程序的并发个数
-
线程池
from concurrent.futures import ThreadPoolExecutor
def func(i):
print('start', os.getpid())
time.sleep(random.randint(1,3))
print('end', os.getpid())
return '%s * %s'%(i,os.getpid())
tp = ThreadPoolExecutor(20)
ret_l = []
for i in range(10):
ret = tp.submit(func,i) #submit
ret_l.append(ret)
tp.shutdown() #
print('main')
for ret in ret_l:
print('------>',ret.result())from concurrent.futures import ThreadPoolExecutor
def func(i):
print('start', os.getpid())
time.sleep(random.randint(1,3))
print('end', os.getpid())
return '%s * %s'%(i,os.getpid())
tp = ThreadPoolExecutor(20)
ret = tp.map(func,range(20)) #map
for i in ret:
print(i)
ret_l = []
for i in range(10):
ret = tp.submit(func,i)
ret_l.append(ret)
tp.shutdown()
print('main')
回调函数
import requests
from concurrent.futures import ThreadPoolExecutor
def get_page(url):
res = requests.get(url)
return {'url':url,'content':res.text}
def parserpage(ret):
dic = ret.result()
print(dic['url'])
tp = ThreadPoolExecutor(5)
url_lst = [
'http://www.baidu.com', # 3
'http://www.cnblogs.com', # 1
'http://www.douban.com', # 1
'http://www.tencent.com',
'http://www.cnblogs.com/Eva-J/articles/8306047.html',
'http://www.cnblogs.com/Eva-J/articles/7206498.html',
]
ret_l = []
for url in url_lst:
ret = tp.submit(get_page,url)
ret_l.append(ret)
ret.add_done_callback(parserpage)
总结:
#ThreadPoolExcutor
# ProcessPoolExcutor
# 创建一个池子
# tp = ThreadPoolExcutor(池中线程/进程的个数)
# 异步提交任务
# ret = tp.submit(函数,参数1,参数2....)
# 获取返回值
# ret.result()
# 在异步的执行完所有任务之后,主线程/主进程才开始执行的代码
# tp.shutdown() 阻塞 直到所有的任务都执行完毕
# map方法
# ret = tp.map(func,iterable) 迭代获取iterable中的内容,作为func的参数,让子线程来执行对应的任务
# for i in ret: 每一个i都是任务的返回值
# 回调函数
# ret.add_done_callback(函数名)
# 要在ret对应的任务执行完毕之后,直接继续执行add_done_callback绑定的函数中的内容,并且ret的结果会作为参数返回给绑定的函数
9.5协程
9.5.1基本概念
-
能在一个线程下多个任务之间来回切换,那么每个任务就是一个协程
遇见I/O就切换
-
特点 : 协程是用户级别的,有我们自己写的python代码来控制的,操作系统不可见.不能利用多核,数据安全
-
在python解释器下 --------协程和线程都不能利用多核,都是在一个cpu上轮流执行
-
由于多线程本身不能利用多核,所以即使是开启了多个线程也只能轮流在一个cpu上执行
-
协程如果把所有任务的IO操作都规避掉,只剩下需要使用CPU的操作,就意味着协程就可以做到提高CPU利用率的效果
-
-
多线程和协程
-
线程
切换需要操作系统,开销大,操作系统不可控,给操作系统压力大,操作系统对IO操作的感知更加灵敏
-
协程
切换需要python代码,开销小,用户操作可控,不会增加操作系统的压力,用户级别能够对IO操作的感知比较低
-
9.5.2两种切换方式
-
原生python完成 yield asyncio
asyncio起一个任务
import asyncio
async def demo(): #起一个任务, async函数是协程函数(异步)
print('start')
awite asyncio.sleep(1) #阻塞,协程函数这里要切换出去,还能保证一会儿再切回来,await 必须写在async函数里,
print('end')
loop = asyncio.get_event_loop() #构建一个事件循环
loop.run_until_complete(demo()) #吧demo任务丢到时间循环中去执行
asyncio起多个任务且没有返回值
import asyncio
async def demo(): #协程方法
print('start')
await asyncio.sleep(1) #阻塞
print('end')
loop = asyncio.get_event_loop() #构建一个事件循环
wait_obj = asyncio.wait([demo(),demo(),demo(),demo()]) #demo()数量不限
loop.run_until_complete(wait_obj)
asyncio起多个任务且有返回值
import asyncio
async def demo(): #协程方法
print('start')
await asyncio.sleep(1) #阻塞
print('end')
return 123
loop = asyncio.get_event_loop()
t1 = loop.create_task(demo())
t2 = loop.create_task(demo())
tasks = [t1,t2]
wait_obj = asyncio.wait(tasks)
loop.run_until_complete(wait_obj)
for t in tasks:
print(t.result())
asyncio起多个任务且有返回值(先回优先)
#谁先回来先取谁的结果
import asyncio
async def demo(i): #协程方法
print('start')
await asyncio.sleep(10-i) #阻塞
print('end')
return i,123
asunc def main():
task_l = []
for i in range(10):
task = asyncio.ensure_future(demo(i))
task_l.append(task)
for ret in asyncio.as_omplete(task_l):
ret =await ret
print(ret)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
#asyncio源码
import asyncio
async def get_url():
reader,writer = await asyncio.open_connection('www.baidu.com',80) #reader他给我发请求,writer我给他发请求
writer.write(b'GET / HTTP/1.1\r\nHOST:www.baidu.com\r\nConnection:close\r\n\r\n') #自己拼接的http协议
all_lines = []
async for line in reader:
data = line.decode()
all_lines.append(data)
html = '\n'.join(all_lines)
return html
async def main():
tasks = []
for url in range(20):
tasks.append(asyncio.ensure_future(get_url()))
for res in asyncio.as_completed(tasks):
result = await res
print(result)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main()) # 处理一个任务
#总结:
1.await 阻塞,协程在这里要切换出去,还能保证一会在切换回来,必须写在async函数里,async函数是一个携程函数,调用时并不执行
2.loop是一个事件循环,所有携程的调度,执行都离不开这个loop
# python原生的底层的协程模块
# 爬虫 webserver框架
# 题高网络编程的效率和并发效果
-
c语言完成的python模块
greenlet模块
import time
from greenlet import greenlet
def eat():
print('wusir is eating')
time.sleep(0.5)
g2.switch()
print('wusir finished eat')
def sleep():
print('小马哥 is sleeping')
time.sleep(0.5)
print('小马哥 finished sleep')
g1.switch()
g1 = greenlet(eat)
g2 = greenlet(sleep)
g1.switch()
gevent
import time
print('-->',time.sleep)
import gevent #gevent不认识time
from gevent import monkey
monkey.patch_all()
def eat():
print('wusir is eating')
print('in eat: ',time.sleep)
time.sleep(1)
print('wusir finished eat')
def sleep():
print('小马哥 is sleeping')
time.sleep(1)
print('小马哥 finished sleep')
g1 = gevent.spawn(eat) # 创造一个协程任务
g2 = gevent.spawn(sleep) # 创造一个协程任务
g1.join() # 阻塞 直到g1任务完成为止
g2.join() # 阻塞 直到g1任务完成为止
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('wusir is eating')
time.sleep(1)
print('wusir finished eat')
def sleep():
print('小马哥 is sleeping')
time.sleep(1)
print('小马哥 finished sleep')
# g1 = gevent.spawn(eat) # 创造一个协程任务
# g3 = gevent.spawn(eat) # 创造一个协程任务
# g2 = gevent.spawn(sleep) # 创造一个协程任务
# g1.join() # 阻塞 直到g1任务完成为止
# g2.join() # 阻塞 直到g1任务完成为止
gevent.joinall([g1,g2,g3])
g_l = []
for i in range(10):
g = gevent.spawn(eat)
g_l.append(g)
gevent.joinall(g_l)
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('wusir is eating')
time.sleep(1)
print('wusir finished eat')
return 'wusir***'
def sleep():
print('小马哥 is sleeping')
time.sleep(1)
print('小马哥 finished sleep')
return '小马哥666'
g1 = gevent.spawn(eat)
g2 = gevent.spawn(sleep)
gevent.joinall([g1,g2])
print(g1.value)
print(g2.value)
第九章 并发编程
9.1操作系统简介
9.1.1手工操作 -穿孔卡片
1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式。此时还没有操作系统的概念。
程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。
-
手工操作的两个特点:
-
用户独占全机。不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低。
-
CPU 等待手工操作。CPU的利用不充分。
-
-
人机矛盾
9.1.2 批处理 - 磁带存储
-
批处理系统
加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理一个或多个用户的作业(包括程序、数据和命令)。
-
特点 : 提高cpu的利用率 , 降低数据的读取时间
-
不足
每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低速的I/O完成状态,致使CPU空闲。
为改善CPU的利用率,又引入了多道程序系统。
9.1.3多道程序系统
-
什么是多道程序系统
指同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。
-
单道程序技术
在A程序计算时,I/O空闲, A程序I/O操作时,CPU空闲(B程序也是同样);必须A工作完成后,B才能进入内存中开始工作,两者是串行的,全部完成共需时间=T1+T2。
-
多道程序技术
将A、B两道程序同时存放在内存中,它们在系统的控制下,可相互穿插、交替地在CPU上运行:当A程序因请求I/O操作而放弃CPU时,B程序就可占用CPU运行,这样 CPU不再空闲,而正进行A I/O操作的I/O设备也不空闲,显然,CPU和I/O设备都处于“忙”状态,大大提高了资源的利用率,从而也提高了系统的效率,A、B全部完成所需时间<<T1+T2。
-
能够在一个任务遇到io操作的时候主动把cpu让出来,给其他的任务使用 ,切换由操作系统完成 ,切换需要占用时间
多道程序设计技术不仅使CPU得到充分利用,同时改善I/O设备和内存的利用率,从而提高了整个系统的资源利用率和系统吞吐量(单位时间内处理作业(程序)的个数),最终提高了整个系统的效率。
9.1.4分时系统
-
把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用。若某个作业在分配给它的时间片内不能完成其计算,则该作业暂时中断,把处理机让给另一作业使用,等待下一轮时再继续其运行。由于计算机速度很快,作业运行轮转得很快,给每个用户的印象是,好象他独占了一台计算机。而每个用户可以通过自己的终端向系统发出各种操作控制命令,在充分的人机交互情况下,完成作业的运行。
-
注意 :分时系统的分时间片工作,在没有遇到IO操作的时候就用完了自己的时间片被切走了(切换需要占用时间),这样的切换工作其实降低了cpu的利用率。但是我们牺牲了一点效率,却实现了多个程序共同执行的效果,提高了用户体验
9.1.5 实时系统
-
系统能够及时响应随机发生的外部事件,并在严格的时间范围内完成对该事件的处理。
-
特点 :及时响应 ,高可靠性
注 : 分时——现在流行的PC,服务器都是采用这种运行模式,即把CPU的运行分成若干时间片分别处理不同的运算请求 linux系统 实时——一般用于单片机上、PLC等,比如电梯的上下控制中,对于按键等动作要求进行实时处理
9.1.6通用操作系统
-
具有多种类型操作特征的操作系统。可以同时兼有多道批处理、分时、实时处理的功能,或其中两种以上的功能。
-
多个程序一起在计算机中执行 ,一个程序如果遇到IO操作,切出去让出CPU,如果没有遇到IO,但是时间片到时了,也会切出去让出CPU
9.2 进程
9.2.1进程的基本概念
-
进程是计算机中的最小资源分配单位,是运行中的程序
-
特点 :数据隔离,创建进程时间开销大 ,销毁进程时间开销大 ,进程之间切换时间开销大
-
在目前完成的一些项目不常用
-
在操作系统中的唯一标识符 :pid
-
注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。
#程序和进程的区别?
程序只是一个文件,进程是这个文件被CPU运行起来了
#为什么进程用的不多,还是要讲?
1.是基本功
2.可能用于非常复杂的数据分析或高级算的程序
3.进程和线程的很多模型很多概念基本一致
# 操作系统引入进程的概念的原因
1.从理论角度看,是对正在运行的程序过程的抽象;
2.从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
9.2.2进程的调度(几个重要算法)
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。
短作业优先算法
先来先服务算法
时间片轮转算法
多级反馈算法
只要第一季队列里有任务,就不会执行第二级队列
9.2.3并行与并发
-
并行
两个程序 两个CPU 每个程序分别占用一个CPU自己执行自己的
看起来是同时执行,实际在每一个时间点上都在各自执行着
-
并发
两个程序 一个cpu 每个程序交替的在一个cpu上执行或者在一个时间点上看,多个程序同时在多个cpu上同时运行
看起来在同时执行,但是实际上仍然是串行
9.2.4 同步,异步,阻塞,非阻塞
-
同步
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,两个任务的状态可以保持一致。调用一个方法要等待这个方法结束
-
异步
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了
。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,
所以它是不可靠的任务序列`。调用一个方法 不等待这个方法结束 也不关心这个方法做了什么 -
阻塞
cpu不工作
-
非阻塞
cpu工作
-
同步阻塞
#例 :conn.recv
socket阻塞的tcp协议 -
同步非阻塞
#自定义一个func函数,但没有I/O操作
socket非阻塞的tcp协议 -
异步非阻塞
#把func函数放到其他任务中执行,但没有I/O操作
#本身任务和func任务各自执行自己的
terminate是一个异步非阻塞 -
异步阻塞
9.2.5进程的三状态图
9.2.6 multiprocessing模块
-
mulprocessing中有一个manager类,封装了所有和进程相关的 数据共享 数据传递,但是对于 字典 列表这一类的数据操作的时候会产生数据不安全,需要加锁解决问题,并且需要尽量少的使用这种方式
-
multi muitiple多元的 processing进程
import os
import time
print('start')
time.seep(10)
print(os.getpid(),os.getppid())
#pid process id 进程id
#ppid parent process id 父进程id# 子进程和父进程
注:在pycharm中启动的所有py程序都是pycharm的子进程进程的开启 :
import os
import time
from multiprocessing import Process
def func():
print('start',os.getpid())
time.sleep(1)
print('end',os.getpid())
if __name__ == '__main__':
p = Process(target=func) #实例化一个对象
p.start() # 异步非阻塞 调用开启进程的方法,但是并不等待这个进程真的开启,开启线程几乎不需要花费时间
print('main :',os.getpid())
#main : 12760
#start 14840
#end 14840#有延迟,print提前
import os
import time
from multiprocessing import Process
def eat():
print('start eating',os.getpid())
time.sleep(1)
print('end eating',os.getpid())
def sleep():
print('start sleeping',os.getpid())
time.sleep(1)
print('end sleeping',os.getpid())
if __name__ == '__main__':
p1 = Process(target=eat) #创建一个即将执行eat函数的进程对象
p1.start() #开启进程
p2 = Process(target=sleep) #创建一个即将执行sleep函数的进程对象
p2.start() #开启进程
print('main :',os.getpid())#有延迟,print不提前
import os
import time
from multiprocessing import Process
def eat():
print('start eating',os.getpid())
time.sleep(1)
print('end eating',os.getpid())
def sleep():
print('start sleeping',os.getpid())
time.sleep(1)
print('end sleeping',os.getpid())
if __name__ == '__main__':
p1 = Process(target=eat) #创建一个即将执行eat函数的进程对象
p1.start() #开启进程
p2 = Process(target=sleep) #创建一个即将执行sleep函数的进程对象
p2.start() #开启进程
print('main :',os.getpid())#无延迟,print提前
import os
import time
from multiprocessing import Process
def eat():
print('start eating',os.getpid())
time.sleep(1)
print('end eating',os.getpid())
def sleep():
print('start sleeping',os.getpid())
time.sleep(1)
print('end sleeping',os.getpid())
if __name__ == '__main__':
p1 = Process(target=eat) #创建一个即将执行eat函数的进程对象
p1.start() #开启进程
p2 = Process(target=sleep) #创建一个即将执行sleep函数的进程对象
p2.start() #开启进程
print('main :',os.getpid())#无延迟,print不提前
import os
import time
from multiprocessing import Process
def eat():
print('start eating',os.getpid())
time.sleep(1)
print('end eating',os.getpid())
def sleep():
print('start sleeping',os.getpid())
time.sleep(1)
print('end sleeping',os.getpid())
if __name__ == '__main__':
p1 = Process(target=eat) #创建一个即将执行eat函数的进程对象
p1.start() #开启进程
p2 = Process(target=sleep) #创建一个即将执行sleep函数的进程对象
p2.start() #开启进程
print('main :',os.getpid()) -
关于name == 'main'的一个错误
windows操作系统
主进程中的func和子进程中的func指向的内存地址不同
在执行完最后一句print('main:',os.getpid())时
-
主进程还没有结束 :而是等待着子进程结束.
-
主进程负责回收子进程的资源
-
如果子进程执行结束,父进程没有回收资源,那么这个子进程会变成一个僵尸进程
-
-
windows操作系统执行开启进程的代码(import执行代码导入方式)
-
ios/linux操作系统执行开启进程的代码(copy方式)
-
子进程和主进程相互隔离
-
主进程如何知道子进程结束?
基于网络或文件
-
开启10个进程,给公司5000个人发邮件,发送完邮件之后,打印一个消息'5000'封邮件已发送.
from muitiprocessing import Process
def send_mail():
print('发送了一封邮件')
if __name__ == '__main__':
for i in range(10):
p =Process(target=send_mail)
p.start()
#少了一个阻塞,直到上面的10封邮件已发送
print('5000封邮件已发送完毕') -
join方法 :阻塞,直到子进程结束就结束
from muitiprocessing import Process
def send_mail():
print('发送了一封邮件')
if __name__ == '__main__':
p =Process(target=send_mail)
p.start() #异步非阻塞
p.join() #同步阻塞,直到p对应的进程结束才结束阻塞
print('5000封邮件已发送完毕')import time
import random
from muitiprocessing import Process
def send_mail(a):
time.sleep(random.random())
print('发送了一封邮件')
if __name__ == '__main__':
l = []
for i in range(10):
p =Process(target=send_mail,args =(i,))
p.start()
l.append(p)
for p in l:
p.join()
print('5000封邮件已发送完毕')
9.2.7 守护进程
-
daemon参数可以把子进程设置成一个守护进程
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.daemon = True
p.start()
#跑完之后什么都没有import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
#p.daemon = True
p.start()
time.sleep(2)
#会一直打印'is alive'
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.daemon = True
p.start() #把p子程序设置成了一个守护进程
time.sleep(2)
#打印四个'is alive'
总结 : 子进程随着主进程的代码结束而结束,所有的子进程都必须在主进程结束前结束,由主进程负责回收资源
Qunun下的生产者消费者模型
-
什么是消费者生产者模型?
把一个产生数据并且处理数据的过程解耦,然后让生产数据的过程和处理数据的过程达到一个工作效率上的平衡,对于中间的容器,在多进程中我们使用队列或者可被join的队列,做到控制数据的量
当数据过剩的时候,队列的大小会控制这生产者的行为;当数据严重不足的时候,队列会控制消费者的行为
-
解耦
-
概念 : 把写在一起的大的功能分成多个小的功能处理
-
原因 : 方便修改,增加可读性
-
-
进程和生产者消费者模型关系
-
一个进程就是一个生产者
-
一个进程就是一个消费者
-
-
队列
-
生产者和消费者之间的容器就是队列
-
基于队列生产的消费者模型
import time,os
import random
from multiprocessing import Process,Queue
def consumer(q): #消费者
while True:
res = q.get()
time.sleep(random.randint(1, 3))
print('\033[45m%s 吃 %s\033[0m' % (os.getpid(), res))
def producer(q): #生产者
for i in range(10):
time.sleep(random.randint(1, 3))
res = '包子%s' % i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' % (os.getpid(), res))
if __name__ == '__main__':
q = Queue()
# 生产者们:即厨师们
p1 = Process(target=producer, args=(q,))
import time,os
import random
from multiprocessing import Process,Queue
def consumer(q): #消费者
while True:
res = q.get()
time.sleep(random.randint(1, 3))
print('\033[45m%s 吃 %s\033[0m' % (os.getpid(), res))
def producer(q): #生产者
for i in range(10):
time.sleep(random.randint(1, 3))
res = '包子%s' % i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' % (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('主')
我们发现,此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。那么如何解决呢?
解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break #收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(q):
for i in range(10):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
q.put(None) #发送结束信号
if __name__ == '__main__':
q=Queue()
#生产者们:即厨师们
p1=Process(target=producer,args=(q,))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
#开始
p1.start()
c1.start()
print('主')
注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号
主进程在生产者生产完毕后发送结束信号None
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break #收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(q):
for i in range(2):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
#生产者们:即厨师们
p1=Process(target=producer,args=(q,))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
#开始
p1.start()
c1.start()
p1.join()
q.put(None) #发送结束信号
print('主')
但上述解决方式,在有多个生产者和多个消费者时,我们则需要用一个很low的方式去解决
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
if res is None:break #收到结束信号则结束
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
def producer(name,q):
for i in range(2):
time.sleep(random.randint(1,3))
res='%s%s' %(name,i)
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
#生产者们:即厨师们
p1=Process(target=producer,args=('包子',q))
p2=Process(target=producer,args=('骨头',q))
p3=Process(target=producer,args=('泔水',q))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
#开始
p1.start()
p2.start()
p3.start()
c1.start()
p1.join() #必须保证生产者全部生产完毕,才应该发送结束信号
p2.join()
p3.join()
q.put(None) #有几个消费者就应该发送几次结束信号None
q.put(None) #发送结束信号
print('主')
生产者消费者实现爬虫的例子
from multiprocessing import Process,Queue
import requests
import re
import json
def producer(q,url):
response = requests.get(url)
q.put(response.text)
def consumer(q):
while True:
s = q.get()
if not s:break
com = re.compile(
'<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
'.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S)
ret = com.finditer(s)
for i in ret:
print({
"id": i.group("id"),
"title": i.group("title"),
"rating_num": i.group("rating_num"),
"comment_num": i.group("comment_num")}
)
if __name__ == '__main__':
count = 0
q = Queue(3)
p_l = []
for i in range(10):
url = 'https://movie.douban.com/top250?start=%s&filter='%count
count+=25
p = Process(target=producer,args=(q,url,)).start()
p_l.append(p)
p = Process(target=consumer, args=(q,)).start()
for p in p_l:p.join()
q.put(None)
JoinableQueue([maxsize])
JoinableQueue队列实现消费之生产者模型
from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
while True:
res=q.get()
time.sleep(random.randint(1,3))
print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了
def producer(name,q):
for i in range(10):
time.sleep(random.randint(1,3))
res='%s%s' %(name,i)
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
q.join() #生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。
if __name__ == '__main__':
q=JoinableQueue()
#生产者们:即厨师们
p1=Process(target=producer,args=('包子',q))
p2=Process(target=producer,args=('骨头',q))
p3=Process(target=producer,args=('泔水',q))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
c1.daemon=True
c2.daemon=True
#开始
p_l=[p1,p2,p3,c1,c2]
for p in p_l:
p.start()
p1.join()
p2.join()
p3.join()
print('主')
#主进程等--->p1,p2,p3等---->c1,c2
#p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
#因而c1,c2也没有存在的价值了,不需要继续阻塞在进程中影响主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了。
和守护线程作对比的时候及生产者消费者模型
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
def son2():
for i in range(5):
print('in son2')
time.sleep(1)
if __name__ == '__main__':
p = Process(target=son1)
p.daemon = True
p.start() #把p子程序设置成了一个守护进程
p2 = Process(target=son2)
P2.start()
time.sleep(2)
#将p设置成守护进程,当主进程结束后,p结束
9.2.8 process类
-
is_alive() 判断一个程序是不是还在运行
-
x.terminate() 不管任务是否完成,立即停止工作进程
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.start()
print(p.is_alive())
time.sleep(1)
p.terminate()
print(p.is_alive())
print(p.is_alive())
print(p.is_alive())
print(p.is_alive())
print(p.is_alive())
#结果见下图import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.start() #异步非阻塞
print(p.is_alive())
time.sleep(1)
p.terminate() #异步非阻塞
print(p.is_alive()) #进程还活着,因为操作系统还没有来得及关闭程序
time.sleep(0.01)
print(p.is_alive()) #操作系统已响应我们要关闭程序的需求,再去检测的时候,得到结果是是进程已结束
总结:提供两种开启进程的方式
-
面向函数
def 函数名():
要在子进程中执行代码
p = Process(target=函数名,args=(参数一,))
p.start()开启进程 -
面向对象
class 类名(Process):
def __init__(self,参数一,参数二):
self.a = 参数一
self.b = 参数二
super().__init__()
def run(self):
要在子进程中执行的代码
p = 类名(参数一,参数二)
p.start()开启进程 #异步非阻塞
p.terminate()结束进程 #异步非阻塞
p.join() #同步阻塞
9.2.9锁
#抢票系统
import json,time
from multiprocessing import Process
def search_ticket(user): #查票
with open('ticket_count')as f:
dict = json.load(f)
print('%s查询结果:%s张余票' %(user,dict['count']))
if __name__ == '__main__':
for i in range(10):
p = Process(target=search_ticket,args=('user%s' %i,))
p.start()
import json,time
from multiprocessing import Process
def search_ticket(user): #查票
with open('ticket_count')as f:
dict = json.load(f)
print('%s查询结果:%s张余票' %(user,dict['count']))
def buy_ticket(user): #买票
time.sleep(0.02)
with open('ticket_count')as f:
dict = json.load(f)
if dict['count'] > 0:
print('%s买到票了' %(user,))
dict['count'] -= 1
else:
print('%s没买到票' %user)
time.sleep(0.02)
with open('ticket_count','w')as f:
json.dump(dict,f)
if __name__ == '__main__':
for i in range(10):
p = Process(target=buy_ticket,args=('user%s' %i,))
p.start()
-
但只有两张票
-
解决方法:加锁
import json,time
from multiprocessing import Process,Lock
def search_ticket(user): #查票
with open('ticket_count')as f:
dict = json.load(f)
print('%s查询结果:%s张余票' %(user,dict['count']))
def buy_ticket(user,lock): #买票
with lock:
# lock.acquire() #给这段代码加上锁
time.sleep(0.02)
with open('ticket_count')as f:
dict = json.load(f)
if dict['count'] > 0:
print('%s买到票了' %(user,))
dict['count'] -= 1
else:
print('%s没买到票' %user)
time.sleep(0.02)
with open('ticket_count','w')as f:
json.dump(dict,f)
# lock.release() #给这段代码解锁
if __name__ == '__main__':
lock = Lock()
for i in range(10):
p = Process(target=buy_ticket,args=('user%s' %i,lock))
p.start()#1.如果在一个并发的场景下,涉及到某部分内容
1.需要修改一些所有的进程共享的数据资源
2.需要加锁来维护数据的安全
#2.在数据安全的基础上,才考虑效率问题
#3.同步存在的意义就是数据的安全性
9.2.10进程之间的数据隔离
from multiprocessing import Process
n = 100
def func():
global n
n -= 1
if __name__ == '__main__':
p_l = []
for i in range(10):
p = Process(target=func)
p.start()
p_l.append(p)
for p in p_l:p.join()
print(n) #不能直接写p.start()原因,是p.start()和print(n) 是异步的,不能保证p意境读完再打印n
9.2.11 进程之间的数据共享
mulprocessing中有一个manager类 ,封装了所有和进程相关的数据共享,数据传递相关的数据类型 但是对于 字典 列表这一类的数据操作的时候会产生数据不安全,需要加锁解决问题,并且需要尽量少的使用这种方式
from multiprocessing import Manager,Process,Lock
def func(dic,lock):
with lock:
dic['count'] -= 1
if __name__ == '__main__':
# m = Manager()
with Manager() as m:
l = Lock()
dic = m.dict({'count':100})
p_l = []
for i in range(100):
p = Process(target=func,args=(dic,l))
p.start()
p_l.append(p)
for p in p_l:p.join()
print(dic)
9.2.12IPC
进程之间的通信 --- IPC(inter process communication)
-
Queue (队列) :先进先出 ,天生数据安全
-
基于文件级别的socket,pickle,lock
-
from multiprocessing import Queue, Process
def func(exp,q):
ret = eval(exp)
q.put(ret)
q.put(ret * 2)
q.put(ret * 4)
if __name__ == '__main__':
q = Queue()
Process(target=func, args=('1+2+3', q)).start()
print(q.get())
print(q.get())
print(q.get())
#6
#12
#24 先进先出
import queue
from multiprocessing import Queue
q = Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5) # 当队列为满的时候再向队列中放数据 队列会阻塞
print('5555555')
try:
q.put_nowait(6) # 当队列为满的时候再向队列中放数据 会报错并且会丢失数据,处理方法,导入queue模块(不常用)
except queue.Full:
pass
print('6666666')
print(q.get())
print(q.get())
print(q.get()) # 在队列为空的时候会发生阻塞
print(q.get()) # 在队列为空的时候会发生阻塞
print(q.get()) # 在队列为空的时候会发生阻塞
try:
print(q.get_nowait()) # 在队列为空的时候 直接报错(常用)
except queue.Empty:pass
-
pipe 管道天生数据不安全
-
基于文件级别的socket,pickle,无锁
-
用于只有一收一发
from multiprocessing import Pipe
pip = Pipe()
pip.send()
pip.recv() -
注 :
不使用,原因所得结果不准
9.3线程
9.3.1线程的基本概念
-
能被cpu调度的最小单位
-
特点 :开销小,数据共享 是进程的一部分,不能脱离线程独立存在 本身可以利用多核(同时执行几段代码)每个进程最少有一个线程,线程负责执行代码,一个进程中的多个线程可以共享这个进程中的数据 ,由操作系统调度
-
后面爬虫常用,前段障碍
-
开销
-
线程的创建,也需要一些开销(一个存储局部(临时)变量的结构,记录状态(切换时))
-
创建,销毁,切换开销过程远小于进程
-
python中线程比较特殊,所以进程也可能被用到
-
-
主线程等待所有子线程结束之后才结束,主线程如果结束了,主进程也就结束了
-
数据隔离还是数据共享
from threading import Thread
n = 100
def func():
global n # 不要在子线程里随便修改全局变量
n-=1
t_l = []
for i in range(100):
t = Thread(target=func)
t_l.append(t)
t.start()
for t in t_l:t.join()
print(n)
9.3.2 cpython解释器的相关知识
9.3.2.1Python程序的执行过程
cpython分两步来运行python程序
-
第一步:把文本形式的源代码解析并编译成字节码;
-
第二步:用一种基于栈的解释器来运行第一步产生的字节码。
9.3.2.2确保Cpython解释器状态保持一致的方法
-
Python采用GIL(global interpreter lock)机制来确保一致性,以防止Cpython受到抢占式多线程切换操作的干扰。Python程序尽管也支持多线程,但由于受到GIL保护,所以同一时刻,只能有一个线程得到执行。这就意味着,多条python线程不能在多个CPU核心上面平行地执行字节码。
-
原因:cpython解释器中特殊的垃圾回收机制
-
GIL锁导致了线程可以并发,不能并行
-
所以使用所线程并不影响高io型的操,作只会对高计算型的程序由效率上的影响
-
遇到高计算 :可采用 解决
-
多进程 + 多线程
-
分布式
-
9.3.2.3 GIL
-
什么是gil
-
全局解释器锁,由cpython解释器提供,导致了一个进程中的多个线程在同一时刻只能有一个线程访问cpu
-
我们先看一下官方对GIL的解释
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
简单的一句话包含了很多信息;
a)在Python众多解释器中,只有Cpython才有GIL,JPython就没有;因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL
归结为Python语言的缺陷。明确一点,GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,Python完全可以不依赖于GIL
9.3.3 threading模块
-
threading模块的函数 :
-
current_thread 在哪个线程中被调用,就返回当前线程的对象
-
enumerate 返回当前活着的线程的对象列表
-
active_count 返回当前活者的线程的个数(活着的线程,包括主线程)
-
#开启单个线程
import os
import time
from threading import Thread
def func():
print('start son thread')
time.sleep(1)
print('end son thread',os.getpid())
p = Thread(target=func)
p.start() #启动线程
print('start',os.getpid())
time.sleep(0.5)
print('end',os.getpid())
#开启多个线程
import os
import time
from threading import Thread
def func(i):
print('start son thread',i)
time.sleep(1)
print('end son thread',i,os.getpid())
for i in range(3):
Thread(target=func,args=(i,)).start() #异步非阻塞
print('main')
join方法
阻塞,直到子线程执行结束
import os
import time
from threading import Thread
def func(i):
print('start son thread',i)
time.sleep(1)
print('end son thread')
t_l = []
for i in range(10):
t = Thread(target=func,args=(i,))
t.start()
t_l.append(t)
for t in t_l:
t.join()
print('子线程执行完毕')
9.3.4使用面向对象的方式启动线程
import os
import time
from threading import Thread
class MyThread(Thread):
def __init__(self,i):
self.i = i
super().__init__()
def run(self):
print('start',self.i,self.ident) #ident 线程ID
time.sleep(1)
print('end',self.i)
for i in range(5):
t = MyThread(i)
t.start()
print(t.ident)
线程里的一些其他方法
import os
import time
from threading import Thread
from threading import current_thread,enumerate,active_count
def func(i):
t = current_thread() #当前线程
print('start son thread',i,t.ident)
time.sleep(1)
print('end son thread',i,os.getpid())
t = Thread(target=func,args=(i,))
t.start()
print(t.ident)
print(current_thread().ident) #主线程线程ID,水性杨花 在哪一个线程里,current_thread()得到的就是这个当前线程的信息
print(enumerate()) #查看当前有多少线程活着
print(active_count()) #=== len(enumerate())活着线程的个数
#terminate 结束进程,但在线程中不能从主线程结束子线程
9.3.5线程和进程的效率差
import time
from threading import Thread
from muitiprocessing import Process
def func(a,b):
c = a+b
if __name__ == '__main__':
start = time.time()
P_L = []
for i in range(100):
p = Process(target=func,args=(i,i*2))
p.start()
P_l.append(p)
for p in p_l:
p.join()
print('process',time.time()-start)
start = time.time()
P_L = []
for i in range(100):
p = Thread(target=func,args=(i,i*2))
p.start()
P_l.append(p)
for p in p_l:
p.join()
print('thread',time.time()-start)
9.3.6 守护线程
守护线程一直等到所有非守护线程结束才结束,除了守护主线程的代码,也会守护子线程
-
非守护线程不结束,主线程也不结束
-
主线程结束了,主进程也结束
-
结束顺序 : 非守护线程结束 -->主线程结束-->主进程结束(-->主进程结束 --> 守护线程也结束)
import time
from threading import Thread
def son1():
while True:
time.sleep(0.5)
print('in son1')
def son2():
for i in range(5):
time.sleep(1)
print('in son2')
t =Thread(target=son1)
t.daemon = True
t.start()
Thread(target=son2).start()
time.sleep(3)
9.3.7锁
-
线程中的数据不安全现象
线程的内存共享
from Threading import thread
a = 0
def add_f():
global a
for i in range(20000):
a += 1
def sub_f():
global a
for i in range(20000):
a -= 1
t1 = Thread(target=add_f)
t1.start()
t2 = Thread(target=sub_f)
t2.start()
t1.join()
t2.join()
#每次得到结果不同,不一定为0 -
dis :dis库是python(默认的CPython)自带的一个库,可以用来分析字节码(cpu指令)
import dis
a = 0
def func():
global a
dis.dis(func) -
即便是线程 即便有GIL 也会出现数据不安全的问题
-
1.操作的是全局变量
-
2.+= -= *= /= 先计算再赋值才容易出现数据不安全的问题, 包括 lst[0] += 1 dic['key']-=1
-
-
加锁会影响程序的执行效率,但是保证了数据的安全
from threading import Thread,Lock
a = 0
def add_f(lock):
global a
for i in range(200000):
with lock:
a += 1
def sub_f(lock):
global a
for i in range(200000):
with lock:
a -= 1
lock = Lock()
t1 = Thread(target=add_f,args=(lock,))
t2 = Thread(target=sub_f,args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
print(a) #0
互斥锁
在一个线程中连续多次acquire会死锁
from threading import Lock
lock = Lock()
lock.acquire()
print('*'*20)
lock.release()
lock.acquire()
print('-'*20)
lock.release()
with lock:
print('-'*20)
死锁现象
例 :科学家吃面问题
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name,noodle_lock,fork_lock):
noodle_lock.acquire()
print('%s抢到面了' %name)
fork_lock.acquire()
print('%s抢到叉子了' %name)
print('%s吃了一口面' %name)
time.sleep(0.1)
fork_lock.release()
print('%s放下叉子了' % name)
noodle_lock.release()
print('%s放下面了' % name)
def eat2(name,noodle_lock,fork_lock):
fork_lock.acquire()
print('%s抢到叉子了' % name)
noodle_lock.acquire()
print('%s抢到面了'%name)
print('%s吃了一口面'%name)
time.sleep(0.1)
noodle_lock.release()
print('%s放下面了' % name)
fork_lock.release()
print('%s放下叉子了' % name)
lst = ['alex','wusir','taibai','yuan']
Thread(target=eat1,args=(lst[0],noodle_lock,fork_lock)).start()
Thread(target=eat2,args=(lst[1],noodle_lock,fork_lock)).start()
Thread(target=eat1,args=(lst[2],noodle_lock,fork_lock)).start()
Thread(target=eat2,args=(lst[3],noodle_lock,fork_lock)).start()
-
死锁现象的发生
-
1.有多把锁.一把以上
-
2.多把锁交替使用
-
-
死锁现象的解决
递归锁
-
在同一个线程中,可以连续acquire多次不会被锁住
-
优点 : 在同一个进程中多次acquire也不会阻塞
-
缺点 : 占用了更多资源
from threading import RLock
rlock = RLock()
rlock.acquire()
print('*'*20)
rlock.acquire()
print('-'*20)
rlock.acquire()
print('*'*20)科学家吃面问题的解决
-
法一:递归锁
-
快速解决问题
-
效率差
-
-
注 :递归锁也可能产生死锁现象(多把递归锁交替使用时)
-
法二:优化代码逻辑
-
可以使用互斥锁解决
-
效率相对好,但自己解决问题速度相对低
-
import time
from threading import RLock,Thread
# noodle_lock = RLock()
# fork_lock = RLock()
noodle_lock = fork_lock = RLock()
print(noodle_lock,fork_lock)
def eat1(name,noodle_lock,fork_lock):
noodle_lock.acquire()
print('%s抢到面了'%name)
fork_lock.acquire()
print('%s抢到叉子了' % name)
print('%s吃了一口面'%name)
time.sleep(0.1)
fork_lock.release()
print('%s放下叉子了' % name)
noodle_lock.release()
print('%s放下面了' % name)
def eat2(name,noodle_lock,fork_lock):
fork_lock.acquire()
print('%s抢到叉子了' % name)
noodle_lock.acquire()
print('%s抢到面了'%name)
print('%s吃了一口面'%name)
time.sleep(0.1)
noodle_lock.release()
print('%s放下面了' % name)
fork_lock.release()
print('%s放下叉子了' % name)
lst = ['alex','wusir','taibai','yuan']
Thread(target=eat1,args=(lst[0],noodle_lock,fork_lock)).start()
Thread(target=eat2,args=(lst[1],noodle_lock,fork_lock)).start()
Thread(target=eat1,args=(lst[2],noodle_lock,fork_lock)).start()
Thread(target=eat2,args=(lst[3],noodle_lock,fork_lock)).start() -
9.3.8 线程的单例模式
import time
from threading import Lock
class A:
__instance = None
lock = Lock()
def __new__(cls,*args,**kwargs):
with cls.lock:
if not cls._instance:
cls.__instance = super().__new__()
return cls.__instance
def __init__(self,name,age):
self.name = name
self.age = age
a1 = A('alex',84)
a2 = A('wusir',73)
改进
import time
from threading import Lock
class A:
__instance = None
lock = Lock()
def __new__(cls, *args, **kwargs):
with cls.lock:
if not cls.__instance:
time.sleep(0.1)
cls.__instance = super().__new__(cls)
return cls.__instance
def __init__(self,name,age):
self.name = name
self.age = age
def func():
a = A('alex', 84)
print(a)
from threading import Thread
for i in range(10):
t = Thread(target=func)
t.start()
递归锁也可能产生死锁现象
9.3.9 队列
queue 不能用于进程
from queue import Queue # 先进先出队列
q = Queue(5)
q.put(0)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print('444444')
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
作业
# 使用多线程 实现一个请求网页 并且把网页写到文件中
# 生产者消费者模型来完成
from queue import LifoQueue # 后进先出队列
# last in first out 栈
lfq = LifoQueue(4)
lfq.put(1)
lfq.put(3)
lfq.put(2)
print(lfq.get())
print(lfq.get())
print(lfq.get())
#2
#3
#1
# 先进先出
# 写一个server,所有的用户的请求放在队列里
# 先来先服务的思想
# 后进先出
# 算法
# 优先级队列
# 自动的排序
# 抢票的用户级别 100000 100001
# 告警级别
from queue import PriorityQueue
pq = PriorityQueue()
pq.put((10,'alex'))
pq.put((6,'wusir'))
pq.put((20,'yuan'))
print(pq.get())
print(pq.get())
print(pq.get())
9.4池
-
概念 : 预先开启固定个数的进程数,当任务来临的时候,直接交给已经开好的进程,让这个进程去执行就可以了.他减少了进程,线程开启,关闭,切换的时间,并且减轻了操作系统的负担.
-
为什么要有池?
进程,线程的开启,关闭,切换都需要时间
concurrent.furtures模块
import os
import time
import random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
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() # 关闭池之后就不能继续提交任务,并且会阻塞,直到已经提交的任务完成
print('main',os.getpid())
import os
import time
import random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
# 任务的参数 + 返回值
def func(i,name):
print('start',os.getpid())
time.sleep(random.randint(1,3))
print('end', os.getpid())
return '%s * %s'%(i,os.getpid())
if __name__ == '__main__':
p = ProcessPoolExecutor(5)
ret_l = []
for i in range(10):
ret = p.submit(func,i,'alex')
ret_l.append(ret)
for ret in ret_l:
print('ret-->',ret.result()) # ret.result() 同步阻塞
print('main',os.getpid())
-
进程池
缺点 ; 一个池中的任务个数限制了我们程序的并发个数
-
线程池
from concurrent.futures import ThreadPoolExecutor
def func(i):
print('start', os.getpid())
time.sleep(random.randint(1,3))
print('end', os.getpid())
return '%s * %s'%(i,os.getpid())
tp = ThreadPoolExecutor(20)
ret_l = []
for i in range(10):
ret = tp.submit(func,i) #submit
ret_l.append(ret)
tp.shutdown() #
print('main')
for ret in ret_l:
print('------>',ret.result())from concurrent.futures import ThreadPoolExecutor
def func(i):
print('start', os.getpid())
time.sleep(random.randint(1,3))
print('end', os.getpid())
return '%s * %s'%(i,os.getpid())
tp = ThreadPoolExecutor(20)
ret = tp.map(func,range(20)) #map
for i in ret:
print(i)
ret_l = []
for i in range(10):
ret = tp.submit(func,i)
ret_l.append(ret)
tp.shutdown()
print('main')
回调函数
import requests
from concurrent.futures import ThreadPoolExecutor
def get_page(url):
res = requests.get(url)
return {'url':url,'content':res.text}
def parserpage(ret):
dic = ret.result()
print(dic['url'])
tp = ThreadPoolExecutor(5)
url_lst = [
'http://www.baidu.com', # 3
'http://www.cnblogs.com', # 1
'http://www.douban.com', # 1
'http://www.tencent.com',
'http://www.cnblogs.com/Eva-J/articles/8306047.html',
'http://www.cnblogs.com/Eva-J/articles/7206498.html',
]
ret_l = []
for url in url_lst:
ret = tp.submit(get_page,url)
ret_l.append(ret)
ret.add_done_callback(parserpage)
总结:
#ThreadPoolExcutor
# ProcessPoolExcutor
# 创建一个池子
# tp = ThreadPoolExcutor(池中线程/进程的个数)
# 异步提交任务
# ret = tp.submit(函数,参数1,参数2....)
# 获取返回值
# ret.result()
# 在异步的执行完所有任务之后,主线程/主进程才开始执行的代码
# tp.shutdown() 阻塞 直到所有的任务都执行完毕
# map方法
# ret = tp.map(func,iterable) 迭代获取iterable中的内容,作为func的参数,让子线程来执行对应的任务
# for i in ret: 每一个i都是任务的返回值
# 回调函数
# ret.add_done_callback(函数名)
# 要在ret对应的任务执行完毕之后,直接继续执行add_done_callback绑定的函数中的内容,并且ret的结果会作为参数返回给绑定的函数
9.5协程
9.5.1基本概念
-
能在一个线程下多个任务之间来回切换,那么每个任务就是一个协程
遇见I/O就切换
-
特点 : 协程是用户级别的,有我们自己写的python代码来控制的,操作系统不可见.不能利用多核,数据安全
-
在python解释器下 --------协程和线程都不能利用多核,都是在一个cpu上轮流执行
-
由于多线程本身不能利用多核,所以即使是开启了多个线程也只能轮流在一个cpu上执行
-
协程如果把所有任务的IO操作都规避掉,只剩下需要使用CPU的操作,就意味着协程就可以做到提高CPU利用率的效果
-
-
多线程和协程
-
线程
切换需要操作系统,开销大,操作系统不可控,给操作系统压力大,操作系统对IO操作的感知更加灵敏
-
协程
切换需要python代码,开销小,用户操作可控,不会增加操作系统的压力,用户级别能够对IO操作的感知比较低
-
9.5.2两种切换方式
-
原生python完成 yield asyncio
asyncio起一个任务
import asyncio
async def demo(): #起一个任务, async函数是协程函数(异步)
print('start')
awite asyncio.sleep(1) #阻塞,协程函数这里要切换出去,还能保证一会儿再切回来,await 必须写在async函数里,
print('end')
loop = asyncio.get_event_loop() #构建一个事件循环
loop.run_until_complete(demo()) #吧demo任务丢到时间循环中去执行
asyncio起多个任务且没有返回值
import asyncio
async def demo(): #协程方法
print('start')
await asyncio.sleep(1) #阻塞
print('end')
loop = asyncio.get_event_loop() #构建一个事件循环
wait_obj = asyncio.wait([demo(),demo(),demo(),demo()]) #demo()数量不限
loop.run_until_complete(wait_obj)
asyncio起多个任务且有返回值
import asyncio
async def demo(): #协程方法
print('start')
await asyncio.sleep(1) #阻塞
print('end')
return 123
loop = asyncio.get_event_loop()
t1 = loop.create_task(demo())
t2 = loop.create_task(demo())
tasks = [t1,t2]
wait_obj = asyncio.wait(tasks)
loop.run_until_complete(wait_obj)
for t in tasks:
print(t.result())
asyncio起多个任务且有返回值(先回优先)
#谁先回来先取谁的结果
import asyncio
async def demo(i): #协程方法
print('start')
await asyncio.sleep(10-i) #阻塞
print('end')
return i,123
asunc def main():
task_l = []
for i in range(10):
task = asyncio.ensure_future(demo(i))
task_l.append(task)
for ret in asyncio.as_omplete(task_l):
ret =await ret
print(ret)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
#asyncio源码
import asyncio
async def get_url():
reader,writer = await asyncio.open_connection('www.baidu.com',80) #reader他给我发请求,writer我给他发请求
writer.write(b'GET / HTTP/1.1\r\nHOST:www.baidu.com\r\nConnection:close\r\n\r\n') #自己拼接的http协议
all_lines = []
async for line in reader:
data = line.decode()
all_lines.append(data)
html = '\n'.join(all_lines)
return html
async def main():
tasks = []
for url in range(20):
tasks.append(asyncio.ensure_future(get_url()))
for res in asyncio.as_completed(tasks):
result = await res
print(result)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main()) # 处理一个任务
#总结:
1.await 阻塞,协程在这里要切换出去,还能保证一会在切换回来,必须写在async函数里,async函数是一个携程函数,调用时并不执行
2.loop是一个事件循环,所有携程的调度,执行都离不开这个loop
# python原生的底层的协程模块
# 爬虫 webserver框架
# 题高网络编程的效率和并发效果
-
c语言完成的python模块
greenlet模块
import time
from greenlet import greenlet
def eat():
print('wusir is eating')
time.sleep(0.5)
g2.switch()
print('wusir finished eat')
def sleep():
print('小马哥 is sleeping')
time.sleep(0.5)
print('小马哥 finished sleep')
g1.switch()
g1 = greenlet(eat)
g2 = greenlet(sleep)
g1.switch()
gevent
import time
print('-->',time.sleep)
import gevent #gevent不认识time
from gevent import monkey
monkey.patch_all()
def eat():
print('wusir is eating')
print('in eat: ',time.sleep)
time.sleep(1)
print('wusir finished eat')
def sleep():
print('小马哥 is sleeping')
time.sleep(1)
print('小马哥 finished sleep')
g1 = gevent.spawn(eat) # 创造一个协程任务
g2 = gevent.spawn(sleep) # 创造一个协程任务
g1.join() # 阻塞 直到g1任务完成为止
g2.join() # 阻塞 直到g1任务完成为止
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('wusir is eating')
time.sleep(1)
print('wusir finished eat')
def sleep():
print('小马哥 is sleeping')
time.sleep(1)
print('小马哥 finished sleep')
# g1 = gevent.spawn(eat) # 创造一个协程任务
# g3 = gevent.spawn(eat) # 创造一个协程任务
# g2 = gevent.spawn(sleep) # 创造一个协程任务
# g1.join() # 阻塞 直到g1任务完成为止
# g2.join() # 阻塞 直到g1任务完成为止
gevent.joinall([g1,g2,g3])
g_l = []
for i in range(10):
g = gevent.spawn(eat)
g_l.append(g)
gevent.joinall(g_l)
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('wusir is eating')
time.sleep(1)
print('wusir finished eat')
return 'wusir***'
def sleep():
print('小马哥 is sleeping')
time.sleep(1)
print('小马哥 finished sleep')
return '小马哥666'
g1 = gevent.spawn(eat)
g2 = gevent.spawn(sleep)
gevent.joinall([g1,g2])
print(g1.value)
print(g2.value)