函数在调用时可以让程序提前结束
-
-
其实只是抛出了一个
SystemExit
异常-
Exit the interpreter by raising SystemExit(status)
-
-
可以捕获异常,拦截程序的过早退出并执行清理活动
-
如果未被捕获,则解释器正常退出
-
raise
语句显示抛出SystemExit
异常和调用sys.exit
-
-
可以被捕获
-
def later(): import sys print("Bye sys world") sys.exit(42) print("Never reached") if __name__ == '__main__': later() """ 程序运行结果 Bye sys world Process finished with exit code 42 """
os.exit()
-
调用进程立即退出,而不是抛出可以捕获或忽略的异常
-
进程退出时不进行输出流缓冲和运行清理处理器(由
atexit
模块定义) -
一般应当只在分支出的子进程上进行,最好不要在整个程序中进行
-
Exit to the system with specified status, without normal exit processing.
-
-
通过分支进程运行程序时,退出状态可以在父进程中通过
os.wait
和os.watipid
os.system
和os.popen
运行shell名称,在退出时获取close()
,之后需要从位掩码中提取(>>8
)
退出状态实际上被包装进返回值的特定比特位置,需要将结果右移8比特才能读取
❌但是在windows中
,不需要进行右移,可以直接读取
""" 分支子进程,用os.wait观察其退出状态 能在Unix上运行,但是在Windows上不能运行 派生线程共享全局变量,但分支进程拥有自己的全局变量副本 分支进程复制并因此共享文件描述符 """ import os exit_stat = 0 def child(): global exit_stat exit_stat += 1 # 复制全局变量,都是0,改变不影响其他分进程 print('Hello from Child', os.getpid(), exit_stat) os._exit(exit_stat) # 发送到父进程的wait函数的退出状态 print("Never reached") def parent(): while True: newpid = os.fork() if newpid == 0: child() else: pid, status = os.wait() print('Parent got', pid, status, (status >> 8)) if input() == 'q': break if __name__ == '__main__': parent() Hello from Child 10271 1 Parent got 10271 256 1 Hello from Child 10272 1 Parent got 10272 256 1 Hello from Child 10273 1 Parent got 10273 256 1 Hello from Child 10274 1 Parent got 10274 256 1 Hello from Child 10275 1 Parent got 10275 256 1
-
-
但是可以调用
_thread.exit()
结束其调用线程 -
_thread.exit()
和sys.exit
相同,也会抛出SystemExit
异常-
所以也可以在线程中使用
sys.exit
-
但是一定不要使用
os._exit()
,会产生奇怪的结果
-
-
线程一般不利用退出状态,而是给模块水平的全局变量复制或原位修改共享的可变对象以发送信号
-
-
简单的文件
-
命令行参数
-
程序退出状态码
-
shell环境变量
-
标准流重定向
-
os.popen
和subporcess
管理的管道流
-
-
Python库提供的进程间通信IPC工具
-
信号允许程序向其他程序发送简单的通知时间
-
匿名管道允许共享文件描述符的线程即相关进程传递数据,但是依赖于Unix下的进程分支模型
-
命名管道映射到系统的文件系统,允许完全不相关的程序进行交流,但是并非所有的Python平台都提供
-
套接字
虽然可以作为线程间通信的手段,但在不共享内存空间的单独进程的作用下,全部能力更能淋漓精致的表现出来
管道更加偏向于在操作系统内部使用
-
读取管道的调用一般会阻塞调用程序,直到数据变得可用,而非返回一个文件尾指示器
-
管道的读取调用总是返回最先写入的数据(队列,先进先出)
-
管道还可以用于同步化管独立程序的执行
分类
-
具名管道
-
通常称为FIFO,计算机上由一个文件代表
-
具名管道是真正的外部文件,进行通信的进程无需相关,可以是独立的启动程序
-
-
匿名管道
-
仅在进程内部存在, 通常和进程分支合用,作为一种在应用程序内部连接父进程与子进程的手段
-
父进程与子进程通过共享的管道文件描述符进行交流,后者为派生的进程所继承
-
匿名管道对线程也适用
-
匿名管道
-
返回的是两端的描述符,分别代表着输入端和输出端
-
但是没有具体的分别
-
只是一端写入,另一端就只能读取
-
-
描述符的读写工具分别是
os.read
和os.write
,传递的是字节字符串 -
读取数据时,不会考虑数据本身是什么,只会按照指定的数量读取数据
-
为了区分信息,在管道中要求一个分割字符
-
可以使用
os.fdopen
将文件描述符封装进一个文件对象 -
通过文件对象的
readline
方法在管道中搜索写一个/n
(read_fd, write_fd) = os.pipe()
## 匿名管道和进程分支 import os, time def child(pipeout): zzz = 0 while True: time.sleep(zzz) msg = ("Spam %03d" %zzz).encode() os.write(pipeout, msg) zzz = (zzz + 1) % 5 # 0,1,2,3,4,0 .。。。 循环 def parant(): pipein, pipeout = os.pipe() if os.fork() == 0 : child(pipeout) else: while True: # 数据发送完之前保持阻塞 line = os.read(pipein, 32) print("Parent %d got [%s] at %s" % (os.getpid(), line, time.time())) parant()
import os import time start_time = time.time() def child(pipeout): zzz = 0 while True: time.sleep(zzz) msg = ("Spam %03d " % zzz).encode() os.write(pipeout, msg) zzz = (zzz + 1) % 5 # 0,1,2,3,4,0 .。。。 循环 def parant(): pipein, pipeout = os.pipe() if os.fork() == 0: # 关闭输入端 os.close(pipein) child(pipeout) else: # 监听管道, os.close(pipeout) # 关闭输出端 # 创建文本模式的文件对象 pipein = os.fdopen(pipein) while True: # 数据发送完之前保持阻塞 # line = os.read(pipein, 32) line = pipein.readline()[:-1] print("Parent %d got [%s] at %s" % (os.getpid(), line, time.time())) parant()
## 匿名管道和线程 import os import time import threading start_time = time.time() def child(pipeout): zzz = 0 while True: time.sleep(zzz) msg = ("Spam %03d" % zzz).encode() os.write(pipeout, msg) zzz = (zzz + 1) % 5 # 0,1,2,3,4,0 .。。。 循环 def parent(pipein): while True: # 数据发送完成之前保持阻塞 line = os.read(pipein, 32) print("Parent %d got [%s] at %s" % (os.getpid(), line, time.time() - start_time)) pi· parent(pipein)
-
-
当需要数据来回流动时,就需要两个管道
-
用一个管道向程序发送请求
-
""" 派生一个子进程/程序,连接我的stdin/stdout和子进程的stdin/stdout 读写映射到派生程序的输入和输出上,类似subprocess目标绑定流 """ import os,sys def spawn(prop, *args): # 获取标准输入输出的文件描述符,一般而言,stdin=0,stdout=1 stdinFd = sys.stdin.fileno() stdoutFd = sys.stdout.fileno() # 创建两个匿名管道,返回输入流,输出流文件描述符 parentStdin, childStdout = os.pipe() childStdin, parentStdout = os.pipe() pid = os.fork() if pid: # 分支之后,在父进程中 # 关闭管道的子进程端 os.close(parentStdin) os.close(parentStdout) # 将parentStdin赋值给stdinFd,就是sys.stdin # 将stdin/stdout用parent表示了 os.dup2(parentStdin, stdinFd) os.dup2(parentStdout, stdoutFd) else: os.close(childStdin) os.close(childStdout) os.dup2(childStdout, stdoutFd) os.dup2(childStdin, stdinFd) args = (prop, ) + args os.execlp(prop, args) assert False, "Excevp Failed" if __name__ == '__main__': mypid = os.getpid() spawn('python', 'pipes-testchild.py', 'spam') print("Hello 1 from parent", mypid) sys.stdout.flush() reply = input() sys.stderr.write("pareat got", reply, ' ') print("Hello 2 from parent", mypid) sys.stdout.flush() reply = sys.stdin.readline() sys.stderr.write("pareat got", reply, ' ')
命名管道
-
作为文件系统中真实的命名文件而存在的长时运行的管道,这种文件被称为具名管道或者FIFO
-
由于FIFO和计算机上的真实文件相关联,对于任何程序来说都是外部文件
-
不依赖于任务间共享的内存,可以作为线程,进程及独立启动的程序间的IPC的机制
-
具名管道创建之后,客户端通过名称打开并通过常用文件读写操作读写数据
-
FIFO是单向流
-
典型操作中,服务器从FIFO中读取数据,客户端对其写入数据
-
可以使用两个FIFO实现双向通信
-
-
FIFO存在于文件系统中,比进程内部的匿名管道持续时间长,还可以被独立的启动程序访问
-
和正常文件不同的时,操作系统同步化FIFO的访问,使之适合IPC
-
更适合作为独立客户端和服务器程序的一般IPC机制
-
FIFO是套接字端口接口的替代机制
-
但是FIFO不支持远程网络连接
-
对FIFO的访问是通过标准的文件接口实现的
-
不支持目前的Window的Python版本
-
os.mkFIFO
创建命名管道,但是Windows不支持
-
不同仅创建了外部文件
-
为了传输数据,需要像标准文件一样打开和处理
""" 命名管道:os.mkfifo在windows下不能用 没有分支的必要,英文ififo文件管道对于进程为外部文件 父进程/子进程中的共享文件描述符(标准输出输入)在这里没有效果 """ import os import sys import time fifoname = 'pipeinfo' def child(): # 作为文件描述符打开fifo' pipeout = os.open(fifoname, os.O_WRONLY) zzz = 0 while True: time.sleep(zzz) msg = ('Span %03d ' % zzz).encode() os.write(pipeout, msg) zzz = (zzz + 1) % 5 def parent(): # 作为文件对象代开fifo pipein = open(fifoname, 'r') while True: line = pipein.readline()[:-1] print("Parent %d got %s at %s" % (os.getpid(), line, time.time())) if __name__ == '__main__': if not os.path.exists(fifoname): os.mkfifo(fifoname) if len(sys.argv) == 1: parent() else: child()
FIFO独立于子进程和父进程存在,不需要进程分支操作
套接字
由Python的scoket
模块实现的,比管道更加泛化的IPC手段
使得我们可以让数据传输在同一台计算机生的不同程序间进行(经由本机层次的全局端口号连接套接字),也可以在互联网的机器上进行(提供机器名及端口号连接套接字)
-
和FIFO的相同之处:
-
套接字是机器水平的全局对象
-
不要求线程或进程间共享内存,因此对独立程序也适用
-
-
和FIFO的不同之处
-
套接字根据端口号进行识别,而非文件系统的路径名称,利用大不相同的非文件API
-
跨平台移植性好
""" 套接字用于跨任务通信,启动线程,相同通过套接字通信;也适用于独立的程序,因为套接字是系统级别的,类似FIFO 套接字传输字节字符串 """ from socket import socket, AF_INET, SOCK_STREAM from time import localtime # 机器上套接字的端口号和机器名 port = 50008 host = "localhost" def server(): sock = socket(AF_INET, SOCK_STREAM) sock.bind(("", port)) # 最多允许5个等待中的客户端 sock.listen(5) while True: # 等待客户端连接 conn, addr = sock.accept() # 从客户端连接读取字节数 data = conn.recv(1024) print(f'收到:[{addr}]::: 的信息==> {data.decode()}') conn.send(f"server 已收到消息:{data.decode()} :: at [{localtime()}]".encode()) def client(name): sock = socket(AF_INET, SOCK_STREAM) sock.connect((host, port)) sock.send(name.encode()) reply = sock.recv(1024) sock.close() print(f'client got [{reply.decode()}]') if __name__ == '__main__': from threading import Thread sthread = Thread(target=server) sthread.daemon = True # 设置为守护进程,主线程不需要等待结束 sthread.start() for i in range(5): Thread(target=client, args=('client%s' % i,)).start()
-
-
套接字倾向于在
""" 套接字在独立的程序间通信 服务器种子进程中允许,为线程和进程中的客户端提供服务器 套接字是机器水平的全局对象,类似fifo,无需内存共享 """ import os import sys from threading import Thread from socket_preivew import server, client mode = int(sys.argv[1]) print(mode) if mode == 1: server() elif mode == 2: client('client: process=%s' % os.getpid()) else: for i in range(5): Thread(target=client, args=('client: process=%s' % i,)).start()
信号
程序生成信号以触发另一个进程中该信号的处理器
信号: 是软件生成的事件,与跨进程的异常类似,不同于异常,信号根据编号来识别,而不是堆栈
某些信号由不常见的系统事件生成,如果信号没有得到处理,可能结束某个程序
其实是Python解释器的作用域之外的,受控于操作系统的非同步事件机制
Python提供了一个Signal
模块,允许程序将Python函数登记为信号事件处理器,在window和Unix平台下都可以使用,但是windows平台下可能定义的待捕获信号较少
-
singal.singal
: 接受信号编号和函数对象,布置该函数为此信号编号抛出时的处理器-
Python在信号出现时自动恢复大多数信号处理器,没有必要在信号处理器内部再次调用这个函数来重新登记处理器
-
-
singal.pause
: 使进程休眠,直到捕获到下一个信号
信号必须避免有明显的使用痕迹
只要使用得当,信号提供一个基于事件的通信机制,当需要告诉程序发送了某些事情而不必传入细节时,推荐使用
""" 在Python中捕获信号 将信号编号N作为命令行参数传入,利用shell命令kill-N pid向这个进程发送信号 大多数信号处理器在捕获信号后转到Python中处理 single在window下可用,不过仅定义了少数几个信号雷系,没有os.kill """ import sys, signal, time def now(): return time.ctime() def onSingle(signum, stackframe): print("Got singal", signum, 'at', now()) signum = int(sys.argv[1]) # 布置信号处理器 signal.signal(signum, onSingle) # while True: signal.pause()