python 从入门到精通——多任务、多线程编程
文章目录
- 4.1多任务编程
- 4.3 基于fork的多进程编程
- 4.3.2进程相关函数
- 4.3.3孤儿和僵尸
- 4.3.4群聊聊天室
- 4.4 multiprocessing 模块创建进程
- 4.4.1 进程创建方法
- 4.4.2 进程池实现
- 4.5 进程间通信(IPC)
- 4.5.1 管道通信(pipe)
- 4.5.2 消息队列
- 4.5.3 共享内存
- 4.5.4 本地套接字
- 4.5.5 信号量(信号灯集)
- 4.6 线程编程(Thread)
- 4.6.1 线程基本概念
- 4.6.2 Threading 模块创建线程
- 4.6.3 线程对象属性
- 4.6.4 自定义线程类
- 4.7 同步互斥
- 4.7.1 线程间通信方法
- 4.7.2 线程同步互斥方法
- 4.7.3 死锁及其处理
4.1多任务编程
1.意义:充分利用计算机多核资源,提高程序的运行效率。
2.实现方案:多进程,多任务。
3.并发和并行:
- 并发:同时处理多个任务,内核在任务间不断的切换,达到好像多个任务被同时执行的效果,实际上每个时刻只有一个任务占用内核。
- 并行:多个任务利用计算机多核资源同时执行,此时多个任务间为并行关系。
心得:为什么并发能提高执行效率?
因为在程序中存在多个IO操作。
##4.2进程 Process
###4.2.1进程理论基础
1.定义:程序在计算机中的一次运行。 - 程序是一个可执行的文件,是静态的占有磁盘。
- 进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。
2.系统的集成是如果产生的——产生过程
- 用户控件通过调用程序接口或者命令发起请求。
- 操作系统接收用户请求命令,开始创建进程。
- 操作系统开始调用计算机资源,确定进程状态等。
- 操作系统将创建的进程提供给用户使用。
3.进程基本概念
- cpu时间片:如果一个进程占有CPU内核,则称这个进程在cpu时间片上。
- PCB(进程控制块):在内存中开辟的一块空间,用于存放进程的基本信息,也用于系统查找识别进程。
- 进程ID:系统为每个进程分配一个大于0的整数,作为进程id,每个进程id不重复。
在Linux查看进程可以使用命令:ps -aux - 父子进程:系统中每一个进程(除了系统初始化进程)都有唯一的父进程,可以有0个或者多个子进程。父子进程关系便于进程管理。
可以查看进程数,linux命令:pstree - 进程状态
- 三态
- 就绪态:进程具备执行条件,等待分配cpu资源。
- 运行态:进程占有cpu时间片正在运行。
- 等待/等待态:进程暂时停止运行,让出CPU。
查看进程装填,在Linux终端中使用命令:ps -aux,其中的status表示状态。
s:表示普通的等待态。D:表示不可中断的等待态(不能被外部信号终端)
- 三态
status | 描述 |
---|---|
S | 等待态 |
R | 执行态 |
D | 等待态 |
T | 等待态 |
Z | 僵尸态 |
status状态后面有的字母代表的意思:
符号 | 示意 |
---|---|
< | 有较高优先级 |
N | 优先级较低 |
+ | 前台进程(在终端有现象显示的) |
s | 会话组组长 |
l | 有多线程的 |
- 进程的运行特征
- 进程可以使用计算机多核资源
- 进程是计算机分配资源的最小单位
- 进程之间运行互不影响,各自独立
- 每个进程拥有独立的空间,各自使用自己空间资源
面试要求:
1.什么是进程?进程和程序之间有什么区别?
2.进程有哪些状态?状态之间如何转化?
4.3 基于fork的多进程编程
(系统层级的编程接口,在Linux或者Unix下进行)
###4.3.1fork的使用
pid = os.fork()
功能:创建新的进程
返回值:整数,如果进程创建失败则返回负数,如果创建成功,则在原有进程中返回新进程的PID,在新进程中返回0
#fork函数演示
import os
pid = os.fork()
if pid < 0:#创建进程失败print("Create process failed")
elif pid == 0:#成功创建子进程print("The new prcess is created")
else:#父进程print("The old process")print("fork test over")
注意:
- 子进程会复制父进程全部进程空间,从fork下一句开始执行。
- 父子进程独立运行,运行顺序不一定。
- 在pycharm中执行必须基于Linux或者Unix系统,在win系统下或报错。
AttributeError: module 'os' has no attribute 'fork'
- 利用父子进程fork返回值的区别,配合if结构让父子进程执行不同的内容几乎是固定分配的。
- 父子进程有各自特有的特征,比如PID、PCB命令集等。
- 父进程fork之间开辟的空间子进程同样拥有,父子进程对各自空间的操作互不影响。
** 心得 **:1.在fork之后会复制父进程的全部代码和空间。
2.复制成功后,父进程自己执行,子进程在代码的fork之后进行,同时子进程fork的返回值是0.
3.父进程和子进程之间的体现在于代码块中if的条件不同,因为父进程的fork返回值不是0。
4.这里的返回值只是返回值,和pid号没有关系。
4.3.2进程相关函数
函数名 | 返回值 | 功能 |
---|---|---|
os.getpid() | 返回当前进程的pid | 获取一个进程的pid |
os.getppid() | 返回父进程的pid | 获取父进程的pid号 |
os._exit(status) | 参数:进程的终止条件 | 结束一个进程 |
sys.exit([status]) | 参数:整数 表示退出状态,字符串 表示退出时打印内容 | 退出进程 |
4.3.3孤儿和僵尸
1.孤儿进程:父进程先与子进程退出,此时子进程成为孤儿进程。
特点:孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理。
2.僵尸进程:子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就被成为僵尸进程。
特点:僵尸进程虽然结束,但是会存留部分PCB在内存中,大量的僵尸内存会浪费系统的内存资源。
3.如何避免僵尸进程产生
- 使用wait函数处理子进程退出。
- 代码示例:自己写 ,P2-1 02:57
"""创建二级子进程,放置僵尸进程
"""
import os
import timedef fun01():for i in range(4):time.sleep(2)print("写代码····")def fun02():for i in range(5):time.sleep(1)print("测试代码····")pdidd = os.fork()
if pdidd < 0:print("Error")
elif pdidd == 0:p = os.fork()#二级子进程 if p == 0:fun02()else:os._exit(0)
else:os.wait()fun01()
- 通过信号处理子进程的退出
- 原理:子进程退出时会发出信号给父进程,如果父进程忽略子进程信号,则系统会自动处理子进程退出。
- 方法:
使用signal模块在父进程创建子进程前写下如下语句:
import signal
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
特点:非阻塞,不会影响父进程运行,可以处理所有子进程退出。
4.3.4群聊聊天室
功能:类似于QQ群功能
- 有人进入聊天室需要输入姓名,姓名不能重复。
- 有人进入聊天室时,其他人会收到通知,XXX进入了聊天室。
- 一个人发消息,其他人会受到XXX:xxxxxxxxxx
- 有人退出聊天室时,则其他人也会收到通知:XXX退出了聊天室。
- 扩展功能:服务器可以向所有用户发送公告,管理员消息:xxxxxxxxxx。
思路:
1.技术点
- 转发模型:客户端——>服务端——>转发给其他客户端
- 网络通信方式:选择UDP通信.
- 保存用户信息:[(name,addr)]或者{name:addr…}
- 收发是同时执行的,不可能是先接受再发送。所以这里可能需要多进程。
2.结构设计
- 采用什么样的数据结构?函数
- 原则:编写一个模块,测试一个功能。
- 注意注释和结构的设计。
3.分析功能模块,制定编写流程
搭建网络连接
进入聊天室
客户端:
- 输入姓名
- 将姓名发送到服务端
- 接受返回的结果,若不允许则重复输入姓名
4.如果允许则进入聊天室。
服务端:
- 接受姓名
- 判断姓名是否存在
- 将结果反馈到客户端
- 如果允许进入聊天室,增加用户信息
- 通知其他用户
聊天
客户端
- 创建新的进程
- 一个进程循环发送消息
- 一个进程循环接受消息
- 服务端:
- 接受请求,判断请求类别
- 将消息转发给其他用户
- 创建新的进程
退出聊天室
- 客户端:
- 输入quit或者ctrl-c退出
- 将请求发送给服务端
- 结束进程(发送消息进程)
- 接收端接收到服务端EXIT,退出,并结束进程(接收消息进程)
- 服务端:
- 接受消息
- 将退出消息告知其他用户
- 给该用户发送EXIT
- 删除用户
- 客户端:
管理员消息
- 接受请求,判断请求类型
- 将消息转发其他用户
4.协议
如果允许进入聊天室,服务端发送OK给客户端。
如果不允许进入聊天室,服务端发送不允许的原因。
请求类别:
- L:进入聊天室
- C:聊天信息
- Q:退出请求
客户端如果输入quit或者ctrl-c,或者esc表示退出
用户存储结构:{name:addr,…}
代码示例:(完整代码)
注意:因为使用fork函数,win下不行,必须在Linux或者Unix下运行,(同时这里并没有图像界面,所以这里只是在终端运行,测试收发消息。)
"""群聊天服务端
"""
import socket,os,sysADDR = ('0.0.0.0',10006)
user = {}
def do_login(s,name,addr):if name in user or "管理员" in name:s.sendto("该用户已经存在".encode(),addr)returns.sendto(b'OK',addr)#通知其他人msg = "欢迎%s进入聊天室!"%namefor i in user:s.sendto(msg.encode(),user[i])#将用户加入user[name] = addrdef do_chat(s,name,text):"""聊天:param s:socket套接字对象:param name:姓名:param text:聊天内容:return:"""msg = "%s : %s"%(name,text)for i in user:if i != name:s.sendto(msg.encode(),user[i])def do_quit(s,name):"""服务端退出聊天室:param s::param name::return:"""msg = "%s退出聊天室"%namefor i in user:if i != name:s.sendto(msg.encode(),user[i])else:s.sendto(b'EXIT',user[i])#将用户删除del user[name]def do_request(sockfd):"""处理请求:param sockfd::return:"""while True:data,addr = sockfd.recvfrom(4029)# print(data.decode())msg = data.decode().split(' ')#区分请求类别if msg[0] == 'L':do_login(sockfd,msg[1],addr)elif msg[0] == 'C':text = ' '.join(msg[2:])do_chat(sockfd,msg[1],text)elif msg[0] == 'Q':do_quit(sockfd,msg[1])#创建网络连接
def main():#套接字sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sockfd.bind(ADDR)pid = os.fork()if pid < 0:returnelif pid == 0:#子进程专门用来发送管理员消息"""注意:由于创建新进程,同时会创建父进程中所有的数据,当然也是会创建user的数据但是,所有添加用户都是在父进程中执行的,所以子进程中的user是一直为空。因为子进程要将管理员消息发送给所有用户,所以?可以考虑,服务端将管理员消息发送给服务端,即发送给自己,通过协议C表示聊天信息,让子进程发送的消息发送给父进程,让父进程去处理。还需要考虑的是:因为是子进程发送给父进程,父进程判断name为“管理员消息”,如果user表中已存在“管理员消息”这个用户该怎么办?"""while True:msg = input("管理员消息:")msg = "C 管理员消息 " + msgsockfd.sendto(msg.encode(),ADDR)else:#请求处理do_request(sockfd)#父进程专门处理客户端请求if __name__ == '__main__':main()
"""群聊天客户端
"""
import socket,os,sysADDR = ('127.0.0.1',10006)
def send_msg(s,name):"""发送消息:param s:socket套接字对象:param name:发送者姓名:return:"""while True:try:text = input("发言:")except KeyboardInterrupt:text = 'quit'if text == 'quit':#表示退出聊天室msg = "Q "+ names.sendto(msg.encode(),ADDR)sys.exit("退出聊天室")msg = "C %s %s"%(name,text)#定义协议C:发言内容s.sendto(msg.encode(),ADDR)
def recv_msg(s):"""接受消息:param s: socket套接字对象:return:"""while True:data,addr = s.recvfrom(4096)#服务端发送exit,表示让客户端退出if data.decode() == "EXIT":sys.exit()print(data.decode())#创建网络连接
def main():sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)while True:name = input(">>")msg = "L " + name#定义请求类别,L为进入聊天室sockfd.sendto(msg.encode(),ADDR)#等待回应data,addr = sockfd.recvfrom(4096)if data.decode() == 'OK':print("已允许进入聊天室")breakelse:print(data.decode())#创建新的进程pid = os.fork()if pid < 0:print("Error")elif pid == 0:send_msg(sockfd,name)else:recv_msg(sockfd)if __name__ == '__main__':main()
心得:
1.主要采用UDP形式,但是是多线程。
2.客户端创建多线程,因为客户端按照之前的学习是先发送再接受,按照这样的顺序,但是,现在可能进入聊天室,但是giant客户端可能还没有发言就有可能接收到其他人发送的消息,所以,这里的收发消息不是按照一定顺序的,它们的程序之间是相互独立的,那么就要使用多进程关系。
- 子进程用于发送消息。
- 父进程用户接受消息。
(当然,这个创建新进程前提条件是,你已经进入到聊天室)
3.发送管理员消息的时候也要注意: - 因为管理员(服务端)是随时随地可以发送管理员消息的。而客户端在收发消息时是处于阻塞状态,所以管理员在发送消息时必须采用新的进程。
- 子进程:专门发送管理员消息(将管理员消息发送给父进程,由父进程处理)
- 父进程:专门收发消息(处理消息)
4.4 multiprocessing 模块创建进程
(python自己封装的一个包)
4.4.1 进程创建方法
1.流程特点
- 将需要子进程执行的事件封装诶函数。
- 通过模块的process类创建进程对象,关联函数。
- 可以通过进程对象设置进程信息及属性。
- 通过进程对象调用 start 启动进程。
- 通过进程对象调用 join 回收进程。
2.接口基本使用
Process()功能:创建进程对象参数:target:绑定要执行的目标函数(必选)args :元组,用于给target函数位置传参,即函数的参数。(可选)kwargs:字典,给target函数键值传参,函数的参数。(可选)name :进程对象名称。(可选)
p.start()功能:启动进程
注意:
启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建。
p.join([timeout])功能:阻塞等待回收函数。(可选参数)p.join(3)3秒后结束。否则知道p退出后进程才会回收。参数:超时时间。
代码示例:
#multiprocess实例
import multiprocessing as mp
from time import sleep#作为子进程函数
def fun01():print("子程序开始执行")sleep(3)print("子程序执行完毕")#创建进程对象
p = mp.Process(target=fun01)
#启动进程
p.start()sleep(2)
print("父进程执行的功能")#回收进程
p.join()
代码示例2:
import multiprocessing as mp
import os
from time import sleepdef fun01():sleep(3)print("吃饭")print(os.getppid(),"------",os.getpid())def fun02():sleep(2)print("睡觉")print(os.getppid(),"------",os.getpid())def fun03():sleep(4)print("打豆豆")print(os.getppid(),"------",os.getpid())thing = [fun01,fun02,fun03]
jobs = []
for th in thing:p = mp.Process(target=th)jobs.append(p)#p由于会被重复赋值,那么可以用列表保存进程对象p.start()for i in jobs:i.join()
代码示例3:
#multiprocess示例3
import multiprocessing as mp
import os
from time import sleep
"""带参数的进程函数
"""
def worker(m,name):""":param m: 秒数:param name: 姓名:return:"""for i in range(3):sleep(m)print("I am %s"%name)print("开始工作了")p = mp.Process(target=worker,args=(2,"Jack"))#位置传参#p = mp.Process(target=worker,kwargs = {'name':"Jack",'s':2})#关键字传参#p = mp.Process(target=worker,args=(2,),kwargs = {'name':"Jack"})#位置传参和关键字传参混合使用
p.start()
p.join()
在win下运行出现RuntimeError错误,但是在Linux下正常运行,可能和fork有关,这里需要深究下。
注意:
- 使用multiprocess创建子进程同样是子进程复制父进程空间和代码段(这里并不是只是函数的代码,连全局变量都会复制),父子进程运行互不影响。
- 子进程只运行target绑定的函数部分,其余内容均是父进程执行的内容。
- multiprocess中父进程往往只用来创建子进程回收子进程,具体事件由子进程完成。(思想上是这样)
- multiprocess创建子进程中无法使用标准输入。(如inpu输入等,否则会报异常)
3.进程对象属性
p.name 集成名称
p.id 对应子进程的pid号
p.is_alive() 查看子进程是否在声明周期,在生命周期则返回True,否则返回False。
p.daemon 设置父子进程的退出关系(守护进程)
- 如果设置为True,则子进程会岁父进程的退出而结束。
- 要求必须在start()前设置。
- 如果daemon设置成True,通常就不会使用join()
4.4.2 进程池实现
1.必要性
- 进程的创建和销毁过程消耗的资源较多。
- 当任务量众多,每个人物在很短时间内完成时,需要频发的创建和销毁过程,此时对计算机压力较大。
- 进程池技术很好的解决了以上问题。
2.原理
创建一定数量的进程来处理事件,事件处理完进程不退出而是继续处理其他事件,知道所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗。
3.进程池实现
- 创建进程池对象,放入适当的进程。
from multiprocess import poolPool(processes)功能:创建进程池对象参数:指定进程数量,默认根据系统自动判定。(可选)
- 将事件加入到进程池队列执行
pool.apply_async(func,args,kwds)功能:使用进程池执行func事件参数:func 事件函数args 元组,给func按位置传参kwds 字典,给func按照键值传参返回值:返回函数事件对象
- 关闭进程池(进程池不再接受新事件)
pool.close()功能:关闭进程池
- 回收进程池中进程(阻塞等待现有的进程执行完毕)
pool.join()功能:回收进程池中进程
代码示例:
from multiprocessing import pool
from time import sleep,ctime
"""
进程池代码示例
"""
#进程池事件
def worker(msg):sleep(2)print(msg)
#创建进程池
pool = pool.Pool()
#进程池中添加事件
for i in range(10):msg = "Hello %d"%ipool.apply_async(func=worker,args= (msg,))#关闭进程池
pool.close()
#回收进程池
pool.join()
4.5 进程间通信(IPC)
1.必要性
进程间空间独立,资源不共享,此时需要在进程间数据传输时就需要特定的手段进行数据通信。
2.常用的进程间通信方法
管道
消息队列
共享内存
信号
信号量
套接字
4.5.1 管道通信(pipe)
1.通信原理
在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现管道通信。
2.实现方法
当问单向管道时,fd1只读,fd2只写
from multiprocess import Pipe
fd1,fd2 = Pipe(duplex = True)功能:创建管道参数:默认表示双向管道如果duplex为False,则表示单向管道返回值:表示管道两端的读写对象如果是双向管道均可读写如果是单向管道,##fd1只读,fd2只写##。
fd.recv()功能:从管道获取内容返回值:获取到的数据
fd.send(data)功能:向管道写入数据参数:要写入的数据,非字节串,只要是数据即可
代码示例:(以下示例只能在Linux或者Unix下运行)
#管道通信示例
import multiprocessing as mp
from multiprocessing import Pipe
import time
import os
#创建管道
fd1,fd2 = Pipe(duplex=True)def fun01(name):time.sleep(3)#向管道写入数据fd1.send({name:os.getpid()})jobs = []
for i in range(5):p = mp.Process(target = fun01,args= (i,))jobs.append(p)p.start()for i in range(5):#读取管道data = fd2.recv()print(data)for i in jobs:i.join()
4.5.2 消息队列
1.通信原理(自己写的模块名称不要叫做queue)
在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
队列:先进先出
使用场景:多个进程对一个进程发送请求时,或者一个进程被多个进程使用时。
2.实现方法
from multiprocess import Queue
q = Queue(maxsize = 0)功能:创建队列对象参数:最多存放消息个数,默认根据系统给定。返回值:队列消息q.put(data,[block,timeout])功能:向队列存入消息参数:data 要存入的内容[block,timeout] block:设置是否阻塞,False 为非阻塞阻塞的情况:如果队列满了timeout:超时检测凡是有timeout的,基本上都是则色函数。q.get([block,timeout])功能:从队列取出消息参数:block:设置是否阻塞,False 为非阻塞timeout:超时检测返回值:返回获取到的内容q.full() #判断队列是否为满
q.empty() #判断队列是否为空
q.qsize() #获取队列中消息个数
q.close() #关闭队列
代码示例:
#消息队列通信
import multiprocessing as mp
from time import sleep
from random import randint
#创建消息队列
q = mp.Queue(3)#消息队列大小
def request():for i in range(10):x = randint(0,100)y = randint(0,100)q.put((x,y))
def handle():while True:sleep(0.5)try:x,y = q.get(timeout=3)except:breakelse:print("%d + %d = %d"%(x,y,x + y))
#相当于两个子进程间的通信
p1 = mp.Process(target=request)
p2 = mp.Process(target=handle)
p1.start()
p2.start()
p1.join()
p2.join()
4.5.3 共享内存
1.通信原理
字啊内存中开辟一块空间,进程可以写入内容或读取内容完成通信,但每次写入内容会覆盖之前内容。
2.实现方法
from multiprocessing import Value,Arrayobj = Value(ctype,data)功能:开辟共享内存参数:ctype:表示共享内存空间类型 'i' 整型,'f' 浮点型,'c' char型data:共享内存空间初始数据返回值:共享内存对象
如:obj = Value('i',10)obj.value 对象属性的修改查看,即对共享内存读写obj = Array(ctype,data)功能:开辟共享内存参数:ctype:表示共享内存数据类型data:整数则表示开辟空间的大小,其他数据类型返回值:共享内存对象Array 共享内存读写,通过遍历obj可以得到每一个值,直接可以通过索引*可以直接使用obj.value直接打印共享内存中的字符串。
代码示例 (这个是存放单一数值,存放字符串的同理):
#共享内存
import multiprocessing as mp
import time
import random
#创建共享内存
money = mp.Value('i',10)
#操作共享内存
def man():for i in range(5):time.sleep(0.5)money.value += random.randint(0,100)
def girl():for i in range(30):time.sleep(1)money.value -= random.randint(5,90)
m = mp.Process(target=man)
g = mp.Process(target=girl)m.start()
g.start()
m.join()
g.join()
#获取共享内存
print("一个月余额:",money.value)
4.5.4 本地套接字
1.功能:
用于本地两个程序之间进行数据的收发,交互时王权不适用网络,而是用本地的套接字文件进行通信。
2.套接字文件
用于本地套接字之间进行通信时,进行数据传输的介质。
扩展内容:
在Linux文件中,cookie:
b c d - l s p
- b:块设备
- c:字符设备
- d:目录
- -:普通文件
- l:连接
- s:套接字文件
- p:管道文件
3.创建本地套接字
- 创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM) #只能在Linux或者Unix中运行
- 绑定本地套接字文件
sockfd,bind(file)
- 监听,接受客户端连接,消息收发
listen() ->accpet() ->recv(),send()
代码示例:
#接收端作为服务端
from socket import *
import os
#确定本地套接字文件
sock_file = './sock'#不需要手动创建,程序运行会自动创建
#判断文件是否存在,存在就删除
if os.path.exists(sock_file):os.remove(sock_file)
#创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
#绑定本地套接字
sockfd.bind(sock_file)
#监听,连接
sockfd.listen(3)
while True:c,addr = sockfd.accept()while True:data = c.recv(1024)if not data:breakprint(data.decode())c.close()
sockfd.close()
#本地套接字
from socket import *
#确保两遍使用相同的套接字文件
sock_file = './sock'
sockfd = socket(AF_UNIX,SOCK_STREAM)
socket.connect(sock_file)
while True:msg = input(">>")if not msg:breaksockfd.send(msg.encode())sockfd.close()
4.5.5 信号量(信号灯集)
1.通信原理 (08.多任务编程P3-2 03:27)
给定一个数量对多个进程可见,多个进程都可以操作该数量增减,并更具数量值决定自己的行为。
2.实现方法 p3-2 03:40
from multiprocessing import Semaphore
sem = Semaphore(num)功能:创建信号量对象参数:信号量的初始值返回值:信号量对象
sem.acquire() #将信号量减1,当信号量为0是阻塞
sem.release() #将信号量加1
sem.get_value() #获取信号量数量
代码示例:
#信号量
from multiprocessing import Semaphore,Process
from time import sleep
import os#创建信号量,服务程序最多允许3个进程同时执行事件
sem = Semaphore(3)def handle():print("%d 想执行事件"%os.getpid())#想执行必须获取信号量sem.acquire()print("%d 开始执行操作!"%os.getpid())sleep(3)print("%d 完成操作"%os.getpid())sem.release()#增加信号量
jobs = []
#10个进程请求执行事件
for i in range(10):p = Process(target=handle)jobs.append(p)p.start()
for i in jobs:i.join()
作业:
1.使用multiprocess创建两个进程,同时复制一个文件中的上下两部分,各自复制到一个新的文件中。
00:46
import multiprocessing as mp
import osclass Copy_file:def __init__(self,file_path):self.file_path = file_pathself.size = os.path.getsize(self.file_path)def top(self,save_path):""":param save_path::return:"""f = open(self.file_path,'rb')n = self.size // 2fw = open(save_path,'wb')fw.write(f.read(n))f.close()fw.close()def bot(self,save_path):""":param save_path::return:"""f = open(self.file_path,'rb')fw = open(save_path,'wb')f.seek(self.size // 2,0)while True:data = f.read(1024)if not data:breakfw.write(data)f.close()fw.close()def main(self):p_top = mp.Process(target=self.top,args = ("top_123.jpg",))p_bot = mp.Process(target=self.bot,args = ("bot_123.jpg",))p_bot.start()p_top.start()p_top.join()p_bot.join()if __name__ == '__main__':cf = Copy_file("123.jpg")cf.main()
注意: 在上述代码中,在top和bot函数中,如果提取f = open(self.file_path,'rb'),使得的打开操作写在进程之外,会有问题,因为写在进程之外,top和bot操作对于file文件的打开会共用同一套文件属性,如偏移量等信息,使得两个进程在分开进行的时候共用一套文件属性复制出来的数据可能有问题。 如果父进程中打开文件,创建进程通信对象,或者创建套接字,而子进程从父进程内存空间获取这些内容,那么父子进程对该对象的操作会有一定的属性关联影响。
4.6 线程编程(Thread)
4.6.1 线程基本概念
1.什么是线程
- 线程被称为轻量级的进程。
- 线程也可以使用计算机多核资源,是多任务变成方式。
- 线程是系统分配内核的最小单元。
- 线程可以理解成进程的分支任务。
2.线程特征
- 一个进程中可以包含多个线程。
- 线程也是一种运行行为,消耗计算机资源。
- 一个进程中所有线程共享这个进程的资源。
- 多个线程之间的运行互不影响各自运行。
- 线程的创建和销毁消耗的资源远小于进程。
- 各个线程也拥有各自的id等特征。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bfXcxcfW-1591682720464)(./img/线程与进程.png)]
4.6.2 Threading 模块创建线程
- 创建线程对象
from threading import Thread
t = Thread()功能:创建线程对象参数:target 绑定线程函数args 元组 给线程函数位置传参kwargs 字典 给线程函数键值传参name 线程名称
- 启动线程
t.start()
- 回收线程
t.join([timeout])
代码示例:
"""
线程示例(主线程和分支线程同时执行)
"""
from threading import Thread
from time import sleep
#线程函数
def music():for i in range(4):sleep(3)print("播放歌曲")#创建线程对象
t = Thread(target= music)
t.start()
#主线程任务
for i in range(3):sleep(2)print("播放电影")t.join()
4.6.3 线程对象属性
- t.name 线程名称
- t.setName() 设置线程名称
- t.getName() 获取线程名称
- t.is_alive() 查看线程是否在声明周期
- t.daemon 设置主线程和分支线程之间的退出关系
- t.setDaemon() 设置daemon属性值
- t.isDaemon() 查看daemon属性值
详解:
daemon 为true时主线程退出分支线程也会退出,要么在start前设置,通常不和join一起使用。
其他:python线程池第三方模块:threadpool
4.6.4 自定义线程类
1.创建步骤:
- 继承Thread类
- 重写__init__方法,添加自己的属性,使用super加载父类属性。
- 重写run方法。
2.使用方法
- 实例化对象
- 调用start自动执行run方法。
- 调用join回收进程。
注意
关于线程函数的高内聚,有可能需要一个线程配合执行多个函数,而t - Thread(target = ?)中只有一个方法参数,如果实现功能实现特别复杂,那么就需要自定义线程类。
代码示例:
from threading import Thread
class ThreadClass(Thread):def __init__(self,attr):self.attr = attrsuper().__init__()#采用父类的init方法#多个方法配合实现具体功能def fun01(self):print("步骤一")def fun02(self):print("步骤二")#重写run方法def run(self):self.fun01()self.fun02()t = ThreadClass("xxxxx")
t.start()
t.join()
代码示例2:
from threading import Thread
from time import sleep,ctimeclass MyThread(Thread):def __init__(self,target = None,args= (),kwargs= {},name = "Tedu"):super().__init__()self.target = targetself.args = argsself.kwargs = kwargsself.name = namedef run(self):self.target(*self.args,**self.kwargs)
###############################
"""通过完成上方MyThread类,让整个程序可以正常运行当调用start时,player作为一个线程功能函数运行注意,函数的名称和参数并不确定,player只是测试函数
"""
def player(sec,song):for i in range(2):print("Playing %s:%s"%(song,ctime()))sleep(sec)
t = MyThread(target = player,args=(3,),kwargs={'song':'凉凉'},name = 'happy')
t.start()
t.join()
4.7 同步互斥
4.7.1 线程间通信方法
1.通信方法
线程间使用全局变量进行通信。
2.共享资源争夺
- 共享资源:多个进程和线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区。
- 影响:对共享资源的无序操作,可能会带来数据的混乱,或者操作失误。此时需要同步互斥机制协调操作顺序。
3.同步互斥机制
同步:同步是一种协调关系,为完成操作,多进程或线程间形成一种协调,按照必要的步骤有序执行操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oyuPxFRA-1591682720468)(./img/同步原理.png)]
互斥:互斥是一种制约机制,当一个进程或线程占有资源时会进行枷锁处理,此时其它进程线程无法操作此资源,直到解锁后才能操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1wolPA2-1591682720469)(./img/互斥原理.png)]
4.7.2 线程同步互斥方法
- 线程event
from threading import Event
e = Event() #创建线程事件对象
e.wait([timeout]) #阻塞等待e被set,timeout超时时间
e.set() #设置e,使得wait结束阻塞
e.clear() #使e回到未被设置状态
e.is_set() #查看当前e是否被设置
代码示例:
#event事件代码示例
from threading import Event
from threading import Thread
from time import sleeps = None #全局变量,模拟对暗号
e = Event()
def 杨子荣():print("杨子荣前来拜上头")global ss = "天王盖地虎"e.set() #共享资源操作完毕t = Thread(target=杨子荣)
t.start()
print("说对口令就是自己人")
#每次验证之前进行阻塞等待
e.wait()
if s == "天王盖地虎":print("宝塔镇河妖")print("口令正确")
else:print("枪毙")
t.join()
- 线程锁 LOCK
from threading import lock
lock = Lock() #创建锁对象
lock.acquire() #上锁 如果lock已经上锁,再调用会阻塞
lock.release() #解锁with lock: #上锁······wait代码块结束后自动解锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lr6HgnVj-1591682720470)(./img/同步互斥原理LOCK.png)]
代码示例:
#同步互斥原理LOCK代码示例from threading import Lock,Threada = b = 0 #全部变量,共享资源
lock = Lock()
def value():while True:lock.acquire()if a != b:print("a = %d,b = %d"%(a,b))lock.release()t = Thread(target=value)
t.start()
while True:with lock:a += 1b += 1
t.join()
4.7.3 死锁及其处理
定义
死锁实质两个或两个以上的线程在执行过程中,由于竞争资源或者彼此通信二造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或者系统产生了死锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t4WmHBjE-1591682720472)(./img/死锁现象.png)]死锁发生的必要条件:
- 互斥条件:指的是线程对所分配的资源进行排它性使用,即在一段时间内某资源只有一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,知道占有资源的进程用完并释放。
- 请求和保持条件:指的是线程已经保持至少一个资源,担忧提出了新的资源请求,而该资源已经被其它集成占有,此时请求线程阻塞,但又对自己已经获得的其它资源保持不放。
- 不剥夺条件:指的是线程已经获得的资源,在使用玩之前不能被剥夺,只能在使用完后由自己释放,通常CPU内存资源是可以被系统强行调配剥夺的。
- 环路等待条件:指的是在发生死锁时,必然会产生一个线程——资源的环形链,即进程集合{t0,t1,t2…tn}中t0正在等待一个t1占用的资源,t1正在等待t2占用的资源…,tn正在等待t0占用的资源。
死锁产生的原因:
- 当线程拥有其他线程需要的资源。
- 当前线程等待其它线程已拥有的资源。
- 都不放弃自己拥有的资源。
阻塞代码示例:
import threading
import time
class Account:"""交易类"""def __init__(self,_id,balance,lock):""":param _id: 用户:param balance: 存款:param lock: 锁"""self.id = _idself.balance = balanceself.lock = lockdef withdraw(self,amount):""":param amount::return:"""self.balance -= amountdef deposit(self,amount):""":param amount::return:"""self.balance += amount#查看账户金额def get_balance(self):return self.balance#转账函数
def transfer(from_,to,amount):if from_.lock.acquire():# 锁着自己的账户,上锁成返回truefrom_.withdraw(amount) # 自己账户金额减少if to.lock.acquire():to.deposit(amount) # 对方账户金额增加to.lock.release()# 对方账户解锁from_.lock.release()# 自身账户解锁print("转账完成")#创建两个账户
Abby = Account('Abby',5000,threading.Lock())
Levi = Account('Levi',3000,threading.Lock())t1 = threading.Thread(target=transfer,args=(Abby,Levi,1500))
t2 = threading.Thread(target=transfer,args=(Levi,Abby,1500))
t1.start()
t2.start()
t1.join()
t1.join()
print("Abby:",Abby.get_balance())
print("Levi:",Levi.get_balance())
- 如何避免死锁
- 使用定时锁
- 使用重入锁RLock(),用法同lock。rlock内部维护这一个lock和counter变量,counter记录了acquire的次数,从而使得资源能够被多次require。直到一个线程的所有acquire都被release,其他线程才能获取资源。
未完待续…
python 从入门到精通——多任务、多线程编程相关推荐
- python编程从入门到精通pdf-码哥—-Python从入门到精通(全60集),资源教程下载...
课程名称 码哥--python从入门到精通(全60集),资源教程下载 课程目录 1Python编程语言历史及特性 2Python编程语言初接触 3Python程序文件结构 4准备Python编程环境 ...
- python和matlab编程 pdf,跟老齐学Python 从入门到精通pdf
摘要 适读人群 :面向初学python的各类人员,包括刚刚接触编程的在校大学生. <跟老齐学Python>试图以比较轻快的风格,向零基础的学习者介绍一门时下比较流行.并且用途比较广泛的编程 ...
- python从入门到精通清华_java从入门到精通(第5版)+python从入门到精通+c语言从入门到精通 全3册 清华大学出版社...
<JAVA从入门到精通(第5版)> <Java从入门到精通(第5版)>从初学者角度出发,通过通俗易懂的语言.丰富多彩的实例,详细介绍了使用Java语言进行程序开发需要掌握的知识 ...
- Python从入门到精通全套完整版教程(懂中文就能学会)
兄弟!毫无套路!!! Python从入门到精通全套完整版教程(懂中文就能学会) 福利分享: 本套视频一共400集,共分4季 第一季 Python基础 第二季 Python深入和扩展 第三季 网络编程. ...
- python从入门到精通最全总结
python从入门到精通最全总结 python项目总结 一.python入门到精通总结 1.pip 安装和使用 1.1安装 1.2使用 1.3pip批量安装软件包 (这是重点单拿出来) 2.virtu ...
- Python从入门到精通 - 入门篇 (下)
上一讲回顾:Python从入门到精通 - 入门篇 (上) 接着上篇继续后面两个章节,函数和解析式. 4 函数 Python 里函数太重要了 (说的好像在别的语言中函数不重要似的).函数的通用好处就不用 ...
- Python 从入门到精通:一个月就够了?真的能行嘛?
毫无疑问,Python 是当下最火的编程语言之一.对于许多未曾涉足计算机编程的领域「小白」来说,深入地掌握 Python 看似是一件十分困难的事.其实,只要掌握了科学的学习方法并制定了合理的学习计划, ...
- python从入门到精通视频(全60集)-【网盘下载】Python从入门到精通视频(全60集)...
Python从入门到精通视频(全60集) JAVA? 今天 课程目录 python入门教程-1-Python编程语言历史及特性 python入门教程-2-Python编程语言初接触 python入门教 ...
- python从入门到精通pdf百度云下载-跟老齐学Python从入门到精通 电子版(pdf格式)...
跟老齐学python从入门到精通是一款由老齐写作的Python电子书籍.书籍讲述了零基础读者的Python入门教程,内容涵盖了Python的基础知识和初步应用,需要的赶紧在巴士下载站下载吧! 目录: ...
- python视频教程从入门到精通全集-python从入门到精通视频(全60集)免费高速下载...
Python是一种面向对象.解释型计算机程序设计语言,由 Guido van Rossum 于1989年底发明,第一个公开发行版发行于1991年,Python 源代码同样遵循 GPL(GNU Gene ...
最新文章
- anki怎么设置学习计划_打篮球怎么训练弹跳力?NBA经典训练计划值得学习
- Xgboost调参小结
- 选项卡,下拉菜单操做时的页面数据更新,highcharts,d3 结合。
- Windows Git客户端搭建
- c语言 已知某系统在通信联络中,数据结构(习题)..doc
- WCF读取配置动态生成客户端对象
- 泰拉瑞亚试图加载不正确的_盘点那些著名的沙盒游戏?泰拉瑞亚堪称2D沙盒之王...
- 2种造成sqlserver自增列不连续的原因
- redis cluster 集群拓展
- 使用纯生js实现图片轮换
- linux跟踪函数代码,linux ltrace-跟踪进程调用库函数的情况
- 浅谈VMD---变分模态分解
- java 种子填充算法_种子填充算法
- arm linux logrotate,交叉编译zlog日志工具:zlog+cronolog+crontab+logrotate
- L1-020. 帅到没朋友
- html5版微博qq登录,QQ和新浪微博登陆第三方的简单实现
- [渝粤教育] 中国地质大学(武汉) 走近国粹 中国陶瓷 参考 资料
- ESP8266 - 首次使用点灯科技
- 计算机网络:网络传输介质
- Oracle存储过程
热门文章
- Docker 学习笔记(八)-- Dockerfile 构建CentOS 实战测试
- WhatsApp使用教程
- js或jquery实现文件下载
- T31开发笔记: 使用FTP上传下载文件
- PS2 键盘 变 USB 键盘 设计图
- 代码本色 processing编程练习
- 【学习方法】 高效记忆秘籍,背书轻松开挂!3个方法专治记不住忘得快,让你像喝水一样高效学习!
- html改变按钮形状6,6个HTML5/CSS3按钮悬停边界旋绕动画
- 记一次企业微信回调解密报错
- 【论文笔记】在CommonsenseQA 上追平人类: 通过External Attention 增强 Self-Attention