python_thread
多任务编程:可以有效的利用计算机资源,同时执行多个任务
进程:进程就是程序在计算机中一次执行的过程
进程 和 程序的区别:
1、程序是一个静态文件的描述,不占计算机的系统资源
2、进程是一个动态的过程,占有cpu、内存等资源,有一定的生命周期
注意:同一个程序的不同执行过程即为不同的进程
问题1、什么决定了进程的创建
答:用户通过应用层程序进行进程的创建申请-->调用操作系统接口进行进程创建
-->告知系统内核创建新的进程提供给应用层使用
问题2、进程如何占有CPU
1、同一个内核同一时刻只能运行一个进程
2、多个进程对内核资源进行抢占,由操作系统内核进行分配
3、哪个进程占有计算机内核,我们称为该进程占有CPU的时间片
问题3、进程在运行过程中的形态和附带内容
1、PCB(进程控制块):在linux和unix操作系统中,进程创建后会在内存中开辟一块空间存放进程的相关信息,这个空间称之PCB
2、PID:在操作系统中进程的唯一标识,是一个大于0的正整数,由系统自动分配
ps -aux 查看进程信息
3、虚拟内存:每个进程占有4G内存地址空间,这里的内存指的是虚拟内存
4、进程状态
1、三态:
1、就绪态:进程具备运行条件,等待系统分派处理器以便运行
2、运行态:进程占有CPU处于运行状态
3、等待态:又称为阻塞态或睡眠态,指进程不具备运行条件,正在等待某些条件的达成
2、五态:在三态的基础上添加两个状态新建态、终止态
1、新建态:创建一个进程的过程,直接表现为执行某个程序或在程序中创建新的进程
2、终止态:进程执行结束,完成回收的过程
D:等待态(不可中断,不会被外界条件所影响)
S:等待态(可中断,会被外界条件所影响)
T:等待态(暂停,无条件的等待)
R:运行态
Z:僵尸态
上面只能选一个,后面的是配合上面使用的
+:前台进程
N:低优先级进程
<:高优先级进程
l:有进程连接
s:会话组
进程的优先级:
1、优先级往往决定了一个进程的执行权限和占有系统资源的优先程度
2、linux系统中优先级范围 -20~19 -20优先级最高
3、用户创建的进程默认优先级为0
4、top:动态查看系统进程运行情况,(NI字段值就是优先级)
< > 可以用来翻页查询
5、nice:以指定的优先级运行某个进程
示例: nice -9 ./while.py #以9的优先级运行程序
sudo nice -9 ./while.py #以-9的优先级运行程序
6、renice n PID:修改一个正在运行的进程的优先级
示例:renice 8 12836 :将12836号的进程优先级修改为8
7、PR:也表示进程的优先级,系统为了控制用户可以修改的优先级范围;PR的范围在:-99~39 与NI的关系:PR = NI + 20
作业控制jobs:
1、jobs:查看后台进程
2、fg:让后台进行在前台运行
3、bg:让暂停的进行在后台运行
4、ctrl + z:让前台的进程在后台暂停
虚拟文件系统:
CPU:/proc/cpuinfo
内存:/proc/meminfo
内核: /proc/cmdline
父子进程:在系统中除了初始化进程之外每个进程都是由父进程创建的,每个进程有一个唯一的父进程,可能有多个子进程
pstree:查看进程树
总结:
1、什么是进程:程序在计算机中一次执行的过程
2、进程和程序的区别:进程是动态的占有cup内存资源,程序是静态文件,不占有从cpu内存资源
3、进程的集中状态及相互间的转换
4、什么是PCB PID CPU时间片
需求:两件不相关的事情希望同时来做
方案1:创建两个进程,分别承担不同的事情,各自执行
分析:
1、两个程序比较麻烦
2、无法确定两个程序应该在什么时间开始运行
方案2:写一个程序,在程序中指定位置用接口来创建新的进程
实现方法:
1、os.fork()函数实现
fork()
1、功能:创建一个新的进程
2、参数:无
3、返回值:
1、<0表示进程创建失败
2、==0在子进程中fork的返回值为0
3、>0在父进程中fork的返回值大于0
fork 是os模块只能在linux和Unix系统下使用
#os 模块提供大量和系统相关的功能函数接口 #os 模块的使用是与系统相关的,在不同的系统中可能有不同的功能 import os print('before craete process') a = 10 pid = os.fork() if pid < 0: print("Create process failed") elif pid == 0: print('This is the new process') print(a)#a=10:父进程中fork之前的内容,子进程同样也会复制,但是父子进程空间内容的修改不会相互影响 print(os.getpid()) a = 1000 else: print('This is the parent process') print(a)#a=10:但是父子进程空间内容的修改不会相互影响 print(pid)#pid的值等于子进程的PID print("The process end")
测试1:父进程中fork之前的内容,子进程同样也会复制,但是父子进程空间内容的修改不会相互影响
测试2:父子进程在执行上互不影响,理论上不一定谁先执行
测试3:子进程虽然复制父进程的空间,但是也有自己独特的特性,比如:自己的PID,进程控制块,进程栈等,父进程中的fork的返回 值即为创建子进程的PID号
进程相关的函数:
1、os.getpid(): 获取当前进程的PID号
2、os.getppid(): 获取当前进程父进程的PID号
3、os._exit(status)
1、功能:结束一个进程
2、参数:一个数字(必须是整数)表示进程的退出状态,通常0表示正常退出,其它数字表示非正常退出
4、sys.exit([status])
1、功能:结束一个进程,如果处理了抛出异常,则不结束进程
2、参数:一个数字表示进程的退出状态,通常0表示正常退出,其它数字表示非正常退出,
还可以是一个字符串,如果是字符串则退出进程会打印这个字符串
import os import sys # os._exit(0) try: #如果处理了抛出异常,则不结束进程 sys.exit(0)#参数可有可无,如果是字符串会打印这个字符串(小数视为字符串), except SystemExit as e: print(e) #次数的参数就是exit传进去的参数 print('process over')
僵尸进程:
1、定义:子进程先于父进程退出,父进程没有对子进程的退出做相应的处理,此时子进程就会变成僵尸进程
2、影响:进程退出后,仍有部分信息残留在内存空间,大量的僵尸进程会影响系统运行, 所以应该尽量避免僵尸进程的产生
孤儿进程:
1、定义:父进程先于子进程退出,此时子进程就会变为孤儿进程
2、影响:当一个进程变为孤儿进程,系统会自动的使用一个进程成为孤儿进程的父进程。当孤儿进程退出时,该系统进程会自动
回收孤儿,使他不会成为僵尸,所以孤儿进程对系统资源没有什么影响
处理僵尸进程的方法:
1、让父进程先退出(不好控制)
2、父进程处理子进程的退出
1、os.wait():
1、功能:等待子进程退出进行处理
2、参数:无
3、返回值:返回一个包含两个元素的元组,第一个是退出的子进程的PID号,第二个是子进程的退出状态
4、wait是一个阻塞函数 即 进程处于等待态,等待某种条件的达成才会继续运行
import os import sys from time import * pid = os.fork() if pid < 0: print('create process failed') elif pid == 0: print('Child process...') print('Child_PID:',os.getpid()) sleep(2) sys.exit("ok11")#子进程退出 else: #wait阻塞等待子进程的退出 p,status = os.wait()#p等于子进程的PID,status等于exit参数为正值时的值乘以256 print(p,status)#如果退出状态是字符串时,status的值为256 print(os.WEXITSTATUS(status))#等于子进程退出的状态 print('Parent process..')
2、os.waitpid(pid,option)
1、功能:同wait 处理子进程退出使其不会变成僵尸
2、参数:pid=-1时 表示等待任意子进程退出
pid>0时 表示等待指定进程号的子进程退出
option=0时 表示阻塞等待
option = os.WNOHANG时 表示 非阻塞等待
import os import sys from time import * pid = os.fork() if pid < 0: print('create process failed') elif pid == 0: print('Child process...') print('Child_PID:',os.getpid()) sleep(2) sys.exit(1)#子进程退出 else: #waitpid非阻塞等待子进程的退出:如果执行到这里刚好这时子进程退出则处理,否则不处理 p,status = os.waitpid(-1,os.WNOHANG)#p等于子进程的PID,status等于exit参数为正值时的值乘以256 print(p,status) print(os.WEXITSTATUS(status))#等于子进程退出的状态 print('Parent process..')
3、创建二级子进程
#创建二级子进程解决僵尸进程 import os pid = os.fork() if pid < 0: print('create process failed') elif pid == 0: p = os.fork()#创建二级子进程 if p < 0: print('process failed') elif p == 0: print('做二级子进程任务') else: os._exit(0)#一级子进程退出,使二级子进程变成孤儿 else: os.wait()#等待一级子进程退出 print('做父进程该做的')
4、在父进程中使用信号处理的方法忽略子进程发来的信号
当子进程结束时会发给一个SIGCHLD信号给父进程,所以在父进程中添加signal(SIGCHLD,SIG_IGN)
import os import time import signal pid = os.fork() if pid < 0: print('create process failed') elif pid == 0: print('子进程的PID:',os.getpid()) else: print('父进程的PID:',os.getpid()) #子进程结束时会发送SIGCHLD信号给父进程,此时父进程忽略(就当没有什么都没有发生),此时子进程就不会变为僵尸进程 signal.signal(signal.SIGCHLD,signal.SIG_IGN) time.sleep(30) print("父进程结束")
总结:
1、函数的使用 fork()/getpid()/getppid()/os._exit/sys.exit/wait/waitpid
2、理解什么是僵尸进程和孤儿进程即两者产生的过程
3、知道僵尸进程的危害和两种处理方法
4、理解进程的创建流程
更方便高效的进程创建方法
multiprocessing模块(标准库模块)
创建进程的步骤:
1、将要完成的事件封装成一个个函数
2、使用multiprocessing提供的接口函数创建进程
3、使新的进程和指定的函数相关联去完成函数中的工作
4、对进程进行回收处理
注意:
1、函数当赋给Process的target变量后函数内容就是对应子进程的进程内容了,此时函数才有特殊性
2、多个子进程和父进程之间的执行互相不影响
import multiprocessing as mp import os import time #创建三个函数,即为将要我完成的事件 def th1(): print(os.getppid(),'---',os.getpid()) print('吃早饭') time.sleep(1) print('出午饭') time.sleep(2) print('吃晚饭') time.sleep(3) def th2(): print(os.getppid(),'---',os.getpid()) print('睡午觉') time.sleep(1) print('睡觉') time.sleep(3) def th3(): print(os.getppid(),'---',os.getpid()) print('打豆豆') time.sleep(2) print('打豆豆') time.sleep(2) #创建3个子进程,生成子进程对象p1/p2/p3 #使用multiprocessing提供的接口函数创建进程,使新的进程和指定的函数相关联去完成函数中的工作 p1 = mp.Process(target = th1) p2 = mp.Process(target = th2) p3 = mp.Process(target = th3) #启动进程让其执行对应的函数事件,该函数事件即为这个进程的内容 p1.start() p2.start() p3.start() print('ParentProcess',os.getpid()) #阻塞等待子进程的退出,然后回收子进程,对进程进行回收处理 p1.join() p2.join() p3.join() print("**********************")
创建子进程:Process()类
1、参数:
target 指定要绑定的函数
name 给创建的进程起一个名字
args 需要一个元组,给target指定的函数传参,按位置传参
kwargs 需要一个字典,给target指定的函数按键值传参
2、进程对象的属性和方法:
1、'进程名称',p.name
2、'进程PID',p.pid
3、'进程状态',p.is_alive()
from multiprocessing import Process import time a = 1 def worker(sec,msg): #当worker作为子进程运行时,对全局变量a的修改只会影响在子进程中a的值,对父进程没有影响 global a a = 1000 for i in range(3): time.sleep(sec) print('the worker msg:',msg) print(a) #args元组中的2将会传给sec,kwargs字典的值传给msg p = Process(name = 'worker',target=worker,args=(2,),kwargs={'msg':'You are a big man'}) p.start() #进程名称 print('进程名称',p.name)#worker print('进程PID',p.pid)#3429 print('进程状态',p.is_alive())#True p.join() print("parent's a :",a)#parent's a : 1
4、daemon 默认值为False,表示主进程运行结束后,不会影响子进程的运行,直到子进程运行完,进程才会结束,如果设 置为True,则主进程运行完毕,所有的子进程也不再运行。哪个子进程设置为True则在主进程结束后该子进程也停止执行,没有设置的子进程照常执行,在这一点上与线程中的daemon是有所不同的
该属性的设置必须要在start()前
该属性如果设置为True,则不再使用join
该属性的设置并不是将进程设置为linux、Unix中的守护进程
守护进程:生命周期长,与前端控制台无关,后台运行一般用作系统进程或者自动化运行进程
启动子进程:start()
1、start()时才真正的创建子进程,而不是Process时创建的
回收子进程:join([timeout])
1、timeout:设置最长阻塞时间,如果超时这个时间还没有子进程退出则不再继续等待
内核会帮助应用层记录子进程的退出情况,当使用join函数时内核会及时返回进程状态给应用层进行处理
import multiprocessing as mp from time import sleep def fun(): print('Child process is start') sleep(3) print('Child proess is end') p1 = mp.Process(target = fun) p2 = mp.Process(target = fun) p1.daemon = True#必须在start之前设置,哪个子进程设置为True则在主进程结束后该子进程也停止执行, p1.start()#没有设置的子进程照常执行 p2.start() sleep(1) print('******main process over*****')
多进程编程
1、优点
1、可以并行的执行多个任务,提高运行效率,空间独立,数据安全
2、创建方便
2、缺点:
1、进程的创建和销毁过程需要消耗较多的计算机资源
2、在需要频繁的创建和删除较多进程的情况下,资源消耗过多,不适宜使用多进程完成任务
进程池技术
产生原因:
如果有大量任务需要对进程完成,且可能需要频繁的创建和删除进程,给计算机带来大量的资源消耗。
原理:
在进程池内运行一定数量的进程,通过这些进程完成进程池队列中的事件,直到事件执行完毕,减少进程不断创建和删除过程
实施操作方法
1、创建进程池,在池内放入适量的进程
2、将事件加入进程池的等待队列
3、使用进程池内的进程不断的执行等待事件
4、所有事件处理结束后关闭回收进程池
Pool(processes):
1、功能:创建进程池
2、参数:processes:进程池中进程的数量,默认为根据运行环的核数确定,并不是进程越多越高效
apply_async()
1、功能:以异步的方式将要执行的事件放入进程池,非阻塞
2、参数:
1、func: 要执行的函数
2、args: 给函数按位置传参
3、kwds:给函数按照键值传参
3、返回值:返回事件执行后的返回值对象,可以通过调用get()函数获取事件函数return的内容
apply()
1、功能:按照顺序添加要执行的事件,执行完一个事件在执行下一个事件,失去了进程池的意思,一般不用,阻塞
·2、参数:
1、func: 要执行的函数
2、args: 给函数按位置传参
3、kwds:给函数按照键值传参
·3、没有返回值
close()
1、功能:关闭进程池,使其不能再加入新的事件,但是之前的添加的要执行完
2、参数:无
join()
1、功能:等待所有子进程结束并处理
2、参数:无
import multiprocessing as mp from time import sleep import os def worker(msg): sleep(2) print(msg) return 'worker return %s'%msg #创建进程池对象,并放入4个的进程,processes是固定的 pool = mp.Pool(processes = 4) results = [] for i in range(10): msg = 'hello%d'%i #向进程池加入要执行的事件,不需要start就直接开始执行 r = pool.apply_async(worker,(msg,)) results.append(r) for res in results: print(res.get())#get方法得到进程时间的返回值,即work函数的返回值 #关闭进程池的事件加入通道,即不能在向进程池中添加事件 pool.close() #阻塞等待进程池处理事件结束后回收进程池 pool.join()
map()
1、功能:类似于内建函数map,将第二个参数的迭代对象中的数据逐个传入第一个函数作为参数。只不过兼顾了apply_async功能,将函数放入进程池
2、返回值:事件函数的返回值列表
from multiprocessing import Pool import time def fun(fn): time.sleep(1) return fn*fn test = [1,2,3,4,5] print('顺序执行:') s = time.time() for i in test: fun(i) e = time.time() print('执行时间:',e-s)#5.004135608673096 pool = Pool(processes = 4) r = pool.map(fun,test)#test参数需要是一个可迭代对象 pool.close() pool.join() print('执行事件',time.time()-e)#2.0332601070404053
创建自己的进程类
1、继承Process类以获取原有的属性
2、实现自己需要的功能部分
3、使用自己的类创建进程即可
from multiprocessing import Process import time class ClockProcess(Process): def __init__(self,value): super().__init__()#等价于Process.__init__(self),执行父类中的__init__方法 self.value = value def run(self): '''在Process类中实现的,现在重写该方法''' n = 5 while n >0: print('The time is {}'.format(time.ctime())) time.sleep(self.value) n -= 1 #使用自己的类创建两个进程 p1 = ClockProcess(2) p2 = ClockProcess(2) #start后会自动执行run()函数 p1.start() p2.start() p1.join() p2.join()
进程间的通信:不同的进程间进行数据的传输
实现方法1:文件进行进程间的通信(和磁盘交互慢,数据不安全)
可行的实现方法:
管道 消息队列 共享内存 信号 套接字 等
1、管道:
在内存中开辟一个管道空间,对多个进程可见,在通信形式上形成一种约束
linux系统下的文件类型:b(块文件)c(设备文件)d(目录)-(普通文件)l(链接)s(套接字)p(管道)
实现方法:multiprocessing---->Pipe函数
Pipe(duplex)
1、功能:创建一个管道
2、参数:duplex默认为True 表示管道为全双工(双向管道),如果为False则表示管道为半双工(单向管道)
3、返回值:返回两个管道流对象,分别表示管道的两端。如果参数为True(默认)两个对象均可发送接收,如果为False则第一个对象只能接收,第二个对象只能 发送。
4、特别注意:
1、向管道发送数据使用send()函数,从管道接收数据使用recv()函数
2、recv()函数为阻塞函数,当管道中数据为空的时候会阻塞
3、一个recv()只能接收一次send()发送的内容
4、send()可以发送字符串、数字、列表等多种类型的数据
from multiprocessing import Pipe,Process import time import os #创建管道对象 child_conn,parent_conn = Pipe() #创建子进程函数 def fun(name): time.sleep(1) #发送一个字符串到管道 print(os.getppid(),'----',os.getpid()) child_conn.send('hello'+str(name)) #创建5个子进程,并放入一个列表中 jobs = [] for i in range(5): p = Process(target = fun,args=(i,)) jobs.append(p) p.start() #冲管道中读取数据,一次只能读一条数据 for i in range(5): data = parent_conn.recv() print(data) # time.sleep(3) # data = parent_conn.recv()#只能取到一条数据 # print(data) #等待子进程结束 for job in jobs: job.join()
2、消息队列:
1、在内存中开辟一个队列模型,用来存放消息,任何拥有队列对象的进程都可以进行消息的存放和取出
2、实现方法:multiprocessing--->Queue函数
Queue(maxsize=0)
1、功能:创建一个消息队列对象
2、参数:maxsize 默认为0,表示消息队列可以存放的消息由系统自动分配的空间而定,如果是大于0的正整数,表示队列中最多存放多少条消息
3、返回值:消息队列对象
q.put():
1、向消息队列存放一条消息,当消息队列满的时候,会阻塞;存放的消息类型可以是数字、列表,字符串等
q.full():
1、判断队列是否满了,如果满了返回True,否则返回False
q.qsize():
1、返回当前队列中消息数量
q.get():
1、获取消息,每次获取一条,当消息队列为空时,则阻塞
q.empty():
1、消息队列为空时返回True,否则返回False
put 和 get中block参数和timeout参数:
1、block 默认为True 表示两个函数都是阻塞函数,如果设置为Fasle则表示不阻塞
2、timeout 当 block设置为True的时候表示超时等待时间
from multiprocessing import Queue #创建消息队列对象 q = Queue(3) #存放消息 i = 0 while True: #判断队列是否满了 if q.full(): print('queue is full') break q.put('hello'+str(i)) i += 1 print("当前队列中的消息数:",q.qsize()) for i in range(q.qsize()): print('消息是:',q.get()) print('是否为空:',q.empty()) try: q.get(True,4)#当队列中没有数据时get函数会阻塞4秒,之后会抛出异常 except Exception as e: print(e) print("proces is over ")
from multiprocessing import Process,Queue import time q = Queue() def fun(name): time.sleep(1) q.put('hello'+str(name)) jobs = [] for i in range(10): p = Process(target=fun,args=(i,)) jobs.append(p) p.start() for job in jobs: job.join() while not q.empty(): print(q.get())
3、共享内存:
在内存开辟一段内存空间存储数据,每次存储的内容会覆盖上次的内容。由于没有对内存进行格式化(对数据进行整理排放)的修饰所以存取速度块的效率高
实现方法1:multiprocessing--->Value
实现方法2:multiprocessing--->Array
1、obj = Value(ctype,obj):
1、功能:开辟新共享内存
2、参数:
1、ctype:要转变的c语言的类型
2、obj: 要写入共享内存的初始值
3、obj.value: 属性获取共享内存中的值
from multiprocessing import Value,Process import time import random def deposite(money): '''存钱函数,将作为一个进程的事件''' for i in range(100): time.sleep(0.03) money.value += random.randint(1,200) def withdraw(money): '''取钱函数,将作为一个进程的事件''' for i in range(100): time.sleep(0.02) money.value -=random.randint(1,150) #开辟一个共享内存 money = Value('i',2000)#将python数据类型转换为C语言的数据类型 d = Process(target=deposite,args=(money,)) w = Process(target=withdraw,args=(money,)) d.start() w.start() d.join() w.join() print(money.value)#取出共享内存中的数据,这个数据是最后一次放进去的数据
2、 obj = Array(ctype,obj)
1、功能:开辟一个共享内存空间
2、参数:
1、ctype 要转变的c语言的数据类型
2、obj:要放入共享内存中的数据,是一个列表,要求列表中的数据为相同类型的数据。
如果obj为一个正整数,则表示在共享内存中开辟一个多大的空间,空间可以存放的数据类型由ctype确定
3、返回值:返回一个可迭代对象可通过for循环取值,可以进行修改值
from multiprocessing import Array,Process import time import ctypes def fun(shm): for i in shm: print(i) shm[0]=1000 #shm = Array('i',[1,2,3,4,5])#第二个参数列表中的数据必须要一致 shm = Array(ctypes.c_char,5)#如果存入的是正整数,则表示在共享内存中开辟一个多大列表(默认列表中的值全为0) p = Process(target = fun,args=(shm,)) p.start() p.join() for i in shm: print(i)
管道 | 消息队列 | 共享空间 | |
开辟空间 | 内存中 | 内存中 | 内存中 |
读写方式 | 可双向/单向 | 先进先出,按照个数储存 | 直接操作内存 |
效率 | 一般 | 一般 | 较快 |
是否需要同步互斥 | 不需要 | 不需要 | 需要 |
4、信号:信号是一种异步的进程间的通信方式
kill -l :查看系统信号
kill -signame PID: 给进程号PID的进程发送signame信号
信号的三要素:
1、信号名称:系统定义
2、含义 :系统定义
3、 默认处理方法:
1、采用默认方式处理(系统定义:终止、暂停、忽略)
2、忽略信号(当信号没有发生过)
3、采用自定义的方式处理
如何发送信号:
1、os.kill(pid,sig)
1、功能:向一个进程发送一个信号
2、参数:pid:要发送进程的PID号;sig:要发送的信号
import os import signal os.kill(19513,signal.SIGKILL)
2、signal.alarm(sec)
1、功能:给自己发送一个时钟信号(SIGALRM)
2、参数 sec:秒数表示在相应的秒数后发送时钟信号
3、alarm函数在一个进程中如果使用多次,则后面的时钟时间会覆盖前面的时间
import signal import time #3秒后给自己发送一个SIGALRM信号 signal.alarm(3) print('ok') signal.alarm(8)#此处的时间会覆盖掉前面的时间 while True: time.sleep(1) print('等待时钟信号')
信号的处理:
1、signal.pause()
1、功能:阻塞等待一个信号的发生
2、signal.signal(signum,handler)
1、功能:处理信号
2、参数:
1、signum:表示可以处理的信号;
2、handler:信号的处理方法:
1、默认处理方式:SIG_DFL
2、忽略信号:SIG_IGN
3、自定义方式:function
3、signal函数也是一个异步处理信号的函数
4、SIGSTOP 和 SIGLILL不能被signal函数处理
''' 1、创建父子进程,分别表示司机和售货员 2、当售票员捕捉到SIGINT(ctrl+c)信号时给司机发送SIGUER1信号,司机打印‘老司机开车了’ 3、等售票员捕捉到SIGQUIT(ctrl+\)信号时给司机发送SIGUSR2信号,司机打印‘系好安全带,小心甩出去’ 4、当司机捕捉到SIGTSTP(ctrl+z)信号时给售票员发送SIGUSR1信号,售票员打印‘到站了,下车吧’ 5、到站后售票员先下车(子进程先退出),然后司机下车 ''' from multiprocessing import Process,Pipe import signal import os import time def conductor(): '''子进程函数即子进程事件''' signal.signal(signal.SIGINT,handler) signal.signal(signal.SIGQUIT,handler) signal.signal(signal.SIGUSR1,handler) #子进程忽视SIGTSTP信号 signal.signal(signal.SIGTSTP,signal.SIG_IGN) while True: time.sleep(1) print('running.....') def handler(sig,frame): '''子进程收到信号做相应的处理''' father_PID = os.getppid() if sig == signal.SIGINT: os.kill(father_PID,signal.SIGUSR1) elif sig == signal.SIGQUIT: os.kill(father_PID,signal.SIGUSR2) elif sig == signal.SIGUSR1: print('到站了下车吧') os._exit(0) def father_handler(sig,frame): '''父进程得到信号做出相应的处理''' if sig == signal.SIGUSR1: print('老司机开车了') elif sig == signal.SIGUSR2: print('系好安全带,小心甩出去') elif sig == signal.SIGTSTP: os.kill(p.pid,signal.SIGUSR1) p = Process(target=conductor) p.start() #接收子进程传递过来的信号 signal.signal(signal.SIGUSR1,father_handler) signal.signal(signal.SIGUSR2,father_handler) signal.signal(signal.SIGTSTP,father_handler) #主进程收到一下信号,均忽视掉 signal.signal(signal.SIGINT,signal.SIG_IGN) signal.signal(signal.SIGQUIT,signal.SIG_IGN) p.join()
同步和互斥:
1、临界资源:对多个进程或者线程都可见的资源,容易产生争夺,我们将这类资源称为临界资源
2、临界区:对临界资源进行操作的代码区域称之为临界区
3、同步 或者 互斥来解决资源争夺
4、同步:同步是一种合作关系,为完成某种任务而建立的多个进程或者线程之间的协调,次序等待,传递消息告知资源占用情况
5、互斥:互斥是一种制约关系,当一个进程或者线程进入到临界区会进行枷锁操作,此时其他进程或线程无法进入临界区,只有
当该进程或线程使用后解锁,其他资源才可以使用,这种技术往往是通过阻塞完成
Event事件:
Event函数
1、e.wait():产生一种阻塞,直到e被set之后才能结束阻塞
2、e.set():将e set操作,wait不再阻塞
3、e.is_set():判断e是否是被设置的状态,被设置返回True,wait不再阻塞,没被设置返回空
4、e.clear():将e变成没有被设置的状态
from multiprocessing import Process,Event import time def wait_event(): print('proces1 is running') e.wait() print('proces1 is running... ') def wait_event_timeout(): print('proces2 is running') e.wait(2) print('timeout,proces2 is running...',e.is_set()) e = Event() p1 = Process(name='block',target=wait_event) p2 = Process(name='non_block',target=wait_event_timeout) p1.start() p2.start() print('parent is running') time.sleep(3) e.set()#将e set操作,wait不再阻塞 print('parent is over')
from multiprocessing import Event #生成时间对象 e = Event() #检测时间对象,如果被设置返回True,否则返回False print(e.is_set())#False #设置事件对象 e.set() print(e.is_set())#True #提供事件的阻塞 e.wait(3) print('wait....') #清除事件的阻塞 e.clear() print(e.is_set())#False e.wait(3) print('&&&&')
进程间同步互斥方法:
multiprocessing--->Lock
lock=Lock()
1、功能:创建进程锁对象
lock.acquire()
1、功能:给临界区上锁
2、具体实现上有一个条件阻塞函数,当有一个进程先进行了acquire操作后,其他进程再企图进行acquire操作时就会阻塞,直到lock对象被release后,其他进程才可以进行下次acquire操作
lock.release()
1、功能:给临界区解锁
说明:
1、可以将lock对象放入到with语句中,当语句开始执行时就会加锁,语句执行结束后自动解锁
2、如果一个进程没有acquire和release(不加锁解锁),则这个进程可以操作这个临界区
from multiprocessing import Process,Lock import time import sys def worker1(stream): lock.acquire()#加锁 for i in range(5): time.sleep(1) stream.write("Lock acquire via\n") lock.release()#解锁 def worker2(stream): #lock.acquire() with lock:#放在with语句块中,语句块开始即加锁,结束即解锁 for i in range(5): time.sleep(1) stream.write('Lock acquire directly\n') #lock.release() lock = Lock() w1 = Process(target = worker1,args=(sys.stdout,)) w2 = Process(target = worker2,args=(sys.stdout,)) w1.start() w2.start() w1.join() w2.join()
线程
1、线程也可以使用计算机的多核资源,也是多任务编程方式之一
2、线程又称轻量级的进程,在并发上和进程相同但是在创建时消耗资源少
3、一个进程可以包含多个线程,这多个线程共享进程的资源
4、多个线程因为共享进程资源所以在通信上往往采用全局变量的方法
5、线程也拥有自己特有的资源,比如TID、指令集等
多进程和多线程的区别和联系:(非常重要需要熟记)
1、都是多任务编程,都可以使用计算机多核
2、进程的创建要比线程消耗更多的资源
3、进程空间独立数据更安全,有专门的进程间通信方式进行交互
4、一个进程包含多个线程,所有线程共享进程资源,没有专门的通信方法,依赖全局变量进行通信。往往需要使用同步互斥机制,逻辑需要考虑更多
5、进程和线程都有自己特有的资源。多个关联任务的时候使用多线程资源消耗更少,如果是多个无关任务也不适于全都使用线程
6、线程是CPU调度和分派的基本单位,进程是操作系统的基本单位
创建线程:
1、threading
2、threading.Tread()
1、功能:创建线程
2、参数:
1、target 线程函数
2、args:以元组方式给线程函数传参
3、kwargs:以字典方式给线程函数传参
4、name:线程名称(默认Thread—1)
import threading from time import ctime,sleep a = 10 def music(sec): print('Listening music') sleep(sec) global a print(a) a = 10000 t = threading.Thread(name='my_thread',target=music,args=(2,)) t.start() print('创建线程') sleep(3) print(a)#1000
3、返回值:返回线程对象
4、t.start(): 启动一个线程
5、t.is_alive():查看一个线程的状态
5、t.name: 查看线程的名字
6、t.join([sec]):阻塞等待回收线程
7、daemon属性:
1、该默认为False主线程执行完毕不会影响其他线程的执行,如果设置为True则主线程执行完毕其他线程也终止执行,
2、如果要是有多个子线程,则每个子线程都要设置为True才会生效,只要有一个没有设置则全都无效
1、t.setDaemon(True)
2、t.daemon=True
3、t.isDaemon():获取属性值
import threading from time import sleep, ctime def fun(): print('This is s thread1 test') sleep(5) print('thread1 over') def fun2(): print('This is s thread2 test') sleep(5) print('thread2 over') t1 = threading.Thread(target=fun,name='my_thread1') t2 = threading.Thread(target=fun2,name='my_thread2') #t1.setDaemon(True) t1.daemon = True t2.daemon = True print('t1_daemon:',t1.isDaemon()) print('t2_daemon:',t2.isDaemon()) t1.start() t2.start() print('t1_name: ',t1.name) print('t1_isalive: ',t1.is_alive())#线程的状态 print('t2_name: ',t2.name) print('t2_isalive:',t2.is_alive())#线程的状态 t1.join(2) print('all over ',ctime())
线程间的通信:
1、全局变量进行通信
'''线程间是通过全局变量进行通信,如果不实现同步互斥方法,会造成数据的混乱''' import threading from time import sleep s = None def bar(): print("呼叫foo") global s s = '天王盖地虎' def foo(): print('foo等口令') sleep(2) print("收到口令",s) def fun(): sleep(1) print('内奸出现') global s s = '小鸡炖蘑菇' t1 = threading.Thread(target= bar,name='bar') t2 = threading.Thread(target=foo,name='foo') t3 = threading.Thread(target=fun,name='fun') t1.start() t2.start() t3.start() t1.join() t2.join() t3.join()
2、线程间的同步互斥方法
1、线程 Event
1、e = threading.Event()
2、e.wait([timeout]):如果e被设置则不会阻塞,未被设置则阻塞,timeout为阻塞的超时时间
3、e.set() :将e变为设置的状态
4、e.clear():将e变为未设置的状态
from threading import * import random from time import sleep a = 500 #创建事件对象 e = Event() def fun(): '''子线程不断减少a,但是不希望a的值小于0''' while True: global a sleep(2) print('a=',a) e.wait() a-=random.randint(0,100) t = Thread(target=fun) t.start() #住线程不断增加a,以确保a的值不会小于0 while True: sleep(1) a+=random.randint(0,10) if a >100: e.set()#让wait不阻塞 else: e.clear()#让wait阻塞 t.join()
2、线程锁
1、lock=threading.Lock()
2、lock.acquire(): 上锁
3、lock.release():解锁
import threading import time a = b =0 lock = threading.Lock() def value(): while True: time.sleep(1) lock.acquire() if a != b: print('a=%d,b=%d'%(a,b)) lock.release() t = threading.Thread(target=value) t.start() lock.acquire() while True: a +=1 b +=1 lock.release() t.join()
创建自己的线程类:
1、自定义类继承原有线程类Thread
2、重写原有的run方法
3、创建线程对象调用start的时候会自动执行run放方法
4、threadpool 线程池第三方模块:
安装方法: sudo pip3 install threadpool
import threading from time import ctime,sleep class MyThread(threading.Thread): def __init__(self,func,args,name='levi'): super().__init__() self.func = func self.args = args self.name = name #自定义线程启动函数 def run(self): self.func(*self.args) def player(file,time): for i in range(2): print('start playing%s:%s'%(file,ctime())) sleep(time) t = MyThread(player,('baby.mp3',3)) t.start() t.join()
GIL(全局解释器锁)
1、python-->支持多线程-->同步和互斥-->加锁-->超级锁-->解释器在同一时刻只能解释一个线程-->大量python库为了省事依赖于这种机制-->python多线程效率低
2、GIL即为python解释器由于上锁带来的同一时刻只能解释一个线程的问题
3、解决方案:
1、不使用线程,转而使用进程
2、不使用c作为解释器,使用Java c#做为python解释器
IO密集型:程序中进行了大量IO操作,只有少量的CPU操作
1、在内存中进行了数据交换的操作都可以认为是IO操作
2、特点:速度较慢,使用CPU不高
cpu密集型(计算密集型):大量的程序都在进行运算操作
1、特点:CPU占有率高
效率测试:
多线程的工作效率和单线程几乎相近,而多进程要比前两者有明显的效率提升
设计模式:
1、设计模式代表了一种最佳实践,是被开发者长期总结,用来解决某一类问题的思路方法。这些方法保证了代码的效率也易于理解,比如:单例模式、工厂模式、生产者模式
2、生产者消费者模式:
1、特点:
1、高内聚:在同一模块内,实现单一功能,尽量不使功能混杂
2、低耦合:不同的模块之间尽量相互独立,减少模块间的影响
3、代码实现
from threading import Thread import queue import time q = queue.Queue()#创建一个队列模型作为商品的仓库 class Producer(Thread): def run(self): count = 0 while True: if q.qsize()<50: for i in range(3): count +=1 msg = '产品 %d'%count q.put(msg)#将产品放入仓库 time.sleep(1) class Customer(Thread): def run(self): while True: if q.qsize()>20: for i in range(2): msg = q.get()#从仓库取出商品 print('消费了一个%s'%msg) time.sleep(1) #创建三个生产者 for i in range(3): p = Producer() p.start() #创建5个消费者 for i in range(5): c = Customer() c.start()
总结:
1、进程和线程的区别
2、会创建使用线程threading
3、掌握基本的线程间同步互斥编程方法
4、知道什么是GIL
5、了解设计模式的概念
面试问题:
1、进程和线程的区别
2、同步和互斥
3、给一个具体的情况,问采用进程还是线程,为什么?
4、你是怎么处理僵尸进程的
5、怎么测试一个硬盘的读写速度
6、xxx框架,是用的多进程还是多线程
7、进程间的通信方式