您现在的位置是:主页 > news > php网站建设管理教材/免费的网站平台
php网站建设管理教材/免费的网站平台
admin2025/4/28 18:55:11【news】
简介php网站建设管理教材,免费的网站平台,深圳网站建设科技有限公司,怎么买做淘宝优惠券网站文章目录进程间通信匿名管道匿名管道的基本操作把管道描述符封装进文件对象匿名管道和线程用匿名管道进行双向*IPC*再次探讨输出流缓冲:死锁和flush命名管道(*FIFO*)命名管道的基本操作命名管道用例初识套接字套接字基本操作套接字和独立程序…
文章目录
- 进程间通信
- 匿名管道
- 匿名管道的基本操作
- 把管道描述符封装进文件对象
- 匿名管道和线程
- 用匿名管道进行双向*IPC*
- 再次探讨输出流缓冲:死锁和`flush`
- 命名管道(*FIFO*)
- 命名管道的基本操作
- 命名管道用例
- 初识套接字
- 套接字基本操作
- 套接字和独立程序
- 套接字用例
- 信号
进程间通信
如果我们限制程序间可以通信的数据类型,下面这些简单的机制都可以理解为进程间通信的手段:
- 简单的文件。
- 命令行参数。
- 程序退出状态代码。
- shell环境变量。
- 标准流重定向。
os.popen
和subprocess
提供的流管道。
举例来说,发送命令行选项和写入输入流可以让我们传入程序执行的参数;读取程序输出流和退出代码给我们提供了一个抓取结果的方法。因为派生程序继承了shell环境变量设置,所以后者又给我们提供了一个传入上下文环境的方法。而由os.popen
或subprocess
创建的管道允许更具动态的通信。
Python库中还有进程间通信(IPC)的其他工具,包括套接字、共享内存、信号、匿名和命名管道。
- 信号允许程序向其他程序发送简单的通知事件。
- 匿名管道允许共享文件描述符的线程及相关进程传递数据,但是一般来说依赖于类Unix下的分支进程模型,而后者不能跨平台移植。
- 命名管道则映射到系统的文件系统,它们允许完全不相关的程序进行交流,但并非所有平台的Python都提供此功能。
- 套接字映射到系统级别的端口号——它们不仅同样允许我们在同一台电脑上任意两个程序传递数据,而且还允许远程联网的机器上的程序之间通信,并且提供更具可移植性的选项。
匿名管道
管道是由操作系统实现的而非Python标准库。它是一个单向的通道,对于其两端来说接口类似一个文件。
管道由两种类型:
- 命名管道(FIFO)在你的计算机上有一个真实的外部文件代表。
- 匿名管道则仅在进程内部存在,通常作为父进程及其子进程通信的手段。
匿名管道的基本操作
os.pipe
创建一个管道,该调用返回一个包含两个文件描述符的元组,这两个文件描述符代表这根管道的输入端和输出端。因为分支出来的子进程复制其父进程的文件描述符,所以在子进程中向管道输出描述符的写入可将数据发回管道的父进程,而这些管道在子进程派生之前就已经创建好了。
示例:pipe_1.py
#!/usr/bin/env python
"用匿名管道从子进程向父进程发送数据"import os
import timedef child(pipe_out_int):"子进程"sleep_int = 0while True:time.sleep(0.01) # 防止向管道输出端发送的数据流重叠time.sleep(sleep_int) # 模拟实际工作,让父进程等待msg_bytes = ('Spam {}'.format(sleep_int)).encode() # 管道是二进制字节os.write(pipe_out_int, msg_bytes) # 发送到父进程sleep_int += 1sleep_int %= 5 # 0到4,4到0def main():pipe_in_int, pipe_out_int = os.pipe() # 创建两个末端的管道if os.fork() == 0: # 复制此进程child(pipe_out_int) # 在副本中运行child()else:while True:msg_bytes = os.read(pipe_in_int, 32) # 数据发送之前保持阻塞print('Parent {} got [{}] at {}'.format(os.getpid(), msg_bytes, time.time()))if __name__ == '__main__':main()
输出:pipe_1.py
Parent 9788 got [b'Spam 0'] at 1628658450.6990933
Parent 9788 got [b'Spam 1'] at 1628658451.710451
Parent 9788 got [b'Spam 2'] at 1628658453.7227986
Parent 9788 got [b'Spam 3'] at 1628658456.7358887
Parent 9788 got [b'Spam 4'] at 1628658460.7503185
Parent 9788 got [b'Spam 0'] at 1628658460.76058
Parent 9788 got [b'Spam 1'] at 1628658461.771917
Parent 9788 got [b'Spam 2'] at 1628658463.784269
Parent 9788 got [b'Spam 3'] at 1628658466.7971053
Parent 9788 got [b'Spam 4'] at 1628658470.8099616
Parent 9788 got [b'Spam 0'] at 1628658470.8200881
Parent 9788 got [b'Spam 1'] at 1628658471.8314338
Parent 9788 got [b'Spam 2'] at 1628658473.843869
...省略...
把管道描述符封装进文件对象
为了更好地区分这些消息(在某些平台上可能会混在一起),我们可以在管道中要求一个分割字符,比如换行符,因为我们可以用os.fdopen
把管道描述符封装进一个文件对象,然后通过该文件对象的readline
方法在管道内搜索下一个\n分隔符。
示例:pipe_2.py
#!/usr/bin/env python
"用匿名管道从子进程向父进程发送数据,并将管道描述符封装进文件对象"import os
import timedef child(pipe_out_int):"子进程"sleep_int = 0while True:# time.sleep(0.01) # 防止向管道输出端发送的数据流重叠time.sleep(sleep_int) # 模拟实际工作,让父进程等待msg_bytes = ('Spam {}\n'.format(sleep_int)).encode() # 管道是二进制字节os.write(pipe_out_int, msg_bytes) # 发送到父进程sleep_int += 1sleep_int %= 5 # 0到4,4到0def main():pipe_in_int, pipe_out_int = os.pipe() # 创建两个末端的管道if os.fork() == 0: # 复制此进程os.close(pipe_in_int) # 在此关闭输入端child(pipe_out_int) # 在副本中运行child()else:os.close(pipe_out_int) # 在此关闭输出端pipe_in_fdfile = os.fdopen(pipe_in_int) # 创建文本模式输入文件对象while True:msg_bytes = pipe_in_fdfile.readline()[:-1] # 数据发送之前保持阻塞print('Parent {} got [{}] at {}'.format(os.getpid(), msg_bytes, time.time()))if __name__ == '__main__':main()
输出:pipe_2.py
Parent 12480 got [Spam 0] at 1628659738.422731
Parent 12480 got [Spam 1] at 1628659739.4239635
Parent 12480 got [Spam 2] at 1628659741.4260693
Parent 12480 got [Spam 3] at 1628659744.4292457
Parent 12480 got [Spam 4] at 1628659748.4317737
Parent 12480 got [Spam 0] at 1628659748.4318242
Parent 12480 got [Spam 1] at 1628659749.43286
Parent 12480 got [Spam 2] at 1628659751.434989
Parent 12480 got [Spam 3] at 1628659754.438162
Parent 12480 got [Spam 4] at 1628659758.4423606
Parent 12480 got [Spam 0] at 1628659758.4424183
Parent 12480 got [Spam 1] at 1628659759.443578
- 这个版本还在各个进程中关闭管道未使用的另一端,正常情况下就是这样。
os.fdopen
的默认r模式,读取操作返回一个文本数据str对象。
匿名管道和线程
示例:pipe_thread.py
#!/usr/bin/env python
"匿名管道和线程而非进程,可在windows上工作"import os
import time
import threadingdef child(pipe_out_int):sleep_int = 0while True:time.sleep(0.01)time.sleep(sleep_int)msg_bytes = ('Spam {}'.format(sleep_int)).encode()os.write(pipe_out_int, msg_bytes)sleep_int += 1sleep_int %= 5def parent(pipe_in_int):while True:msg_bytes = os.read(pipe_in_int, 32)print('Parent {} got [{}] at {}'.format(os.getpid(), msg_bytes, time.time()))def main():pipe_in_int, pipe_out_int = os.pipe()threading.Thread(target=child, args=(pipe_out_int,)).start()parent(pipe_in_int)if __name__ == '__main__':main()
输出:pipe_thread.py
Parent 14472 got [b'Spam 0'] at 1628660580.0414195
Parent 14472 got [b'Spam 1'] at 1628660581.052756
Parent 14472 got [b'Spam 2'] at 1628660583.0650897
Parent 14472 got [b'Spam 3'] at 1628660586.0784411
Parent 14472 got [b'Spam 4'] at 1628660590.0928197
Parent 14472 got [b'Spam 0'] at 1628660590.1031415
Parent 14472 got [b'Spam 1'] at 1628660591.1144705
Parent 14472 got [b'Spam 2'] at 1628660593.1267684
Parent 14472 got [b'Spam 3'] at 1628660596.140166
Parent 14472 got [b'Spam 4'] at 1628660600.1516814
Parent 14472 got [b'Spam 0'] at 1628660600.1618528
Parent 14472 got [b'Spam 1'] at 1628660601.1731
用匿名管道进行双向IPC
用一个管道向程序发送请求,另一个向请求者发回答复。
示例:pipes.py
#!/usr/bin/env python
"""
派生一个子进程/程序,连接到我的stdin/stdout和子进程的stdin/stdout,
我的读写映射到派生程序的输出和输入上;很像利用subprocess模块绑定流一样
"""import os
import sysdef spawn(prog_str, *args_tuple): # 传入程序名称,命令行参数# 获取流的描述符stdin_fd_int = sys.stdin.fileno()stdout_fd_int = sys.stdout.fileno()# 创建两个IPC管道,用于双向通信parent_in_fd_int, child_out_fd_int = os.pipe()child_in_fd_int, parent_out_fd_int = os.pipe()pid_int = os.fork()if pid_int:"父进程"# 在父进程中关闭子进程端os.close(child_in_fd_int)os.close(child_out_fd_int)# 复制流os.dup2(parent_in_fd_int, stdin_fd_int)os.dup2(parent_out_fd_int, stdout_fd_int)else:"子进程"# 在子进程中关闭父进程端os.close(parent_in_fd_int)os.close(parent_out_fd_int)# 将管道的输入和输出流os.dup2(child_in_fd_int, stdin_fd_int)os.dup2(child_out_fd_int, stdout_fd_int)args_tuple = (prog_str,) + args_tupleos.execvp(prog_str, args_tuple) # 复制到子进程的程序assert False, "Execvp failed!" # 如果execvp调用失败,则会中断子进程def main():my_pid_int = os.getpid()spawn('python', 'pipes_test_child.py', 'spam') # 分支子程序print('Hello 1 from parent', my_pid_int) # 发送到子进程的stdinsys.stdout.flush() # 清理stdout缓冲区reply_str = input() # 发自子进程的stdoutsys.stderr.write('Parent got: "{}"\n'.format(reply_str))print('Hello 2 from parent', my_pid_int)sys.stdout.flush()reply_str = sys.stdin.readline()sys.stderr.write('Parent got: "{}"\n'.format(reply_str[:-1]))if __name__ == '__main__':main()
- os.dup2(fd1, fd2):把文件描述符fd1命名的文件的所有相关系统信息复制到由fd2命名的文件中。
示例 :pipes_test_child.py
#!/usr/bin/env python
"pipes.py的测试子程序"import os
import time
import sysdef main():my_pid_int = os.getpid()parent_pid_int = os.getppid()sys.stderr.write('Child {} of {} got arg: "{}"\n'.format(my_pid_int, parent_pid_int, sys.argv[1]))for i_int in range(2):time.sleep(3)reply = input()time.sleep(3)send_str = 'Child {} got: [{}]'.format(my_pid_int, reply)print(send_str)sys.stdout.flush()if __name__ == '__main__':main()
输出:pipes.py
Child 20267 of 20266 got arg: "spam"
Parent got: "Child 20267 got: [Hello 1 from parent 20266]"
Parent got: "Child 20267 got: [Hello 2 from parent 20266]"
再次探讨输出流缓冲:死锁和flush
如果将上述程序所有的file.fulsh
语句注释掉,那么最后程序会困在一个死锁状态里,因为输出流会被缓冲保存起来,不会发送,因此两者都阻塞在输出调用上,程序永远不会继续运行。
有多种方法可以用来避免死锁:
flush
- 参数
open
模式- 命令管道
- 套接字
- 工具
命名管道(FIFO)
在某些系统中可以创建一个作为文件系统里真实的命名文件而存在的长时间运行的管道,这种文件叫做命名管道(FIFO),它和匿名管道基本一样。它可以用作线程、进程及独立启动的程序间的IPC机制。FIFO管道更适合作为独立客户端和服务器程序的一般IPC机制,它是套接字端口的替代机制,但目前不在Windows下Python标准版本中。
命名管道的基本操作
FIFO由os.mkFIFO
创建。
示例:pipe_fifo.py
#!/usr/bin/env python
"命名管道的测试程序:运行命令无参数时用parent监听,有参数则用child写入管道"import os
import time
import sysFIFO_PATH_STR = '/tmp/pipefifo'def child():"模拟客户端程序:向服务器数据发送数据"# print('Start child')pipe_out_int = os.open(FIFO_PATH_STR, os.O_WRONLY)sleep_int = 0while True:time.sleep(sleep_int)msg_bytes = 'Spam {}\n'.format(sleep_int).encode()os.write(pipe_out_int, msg_bytes)# print('Child write "{}"'.format(msg_bytes))sleep_int = (sleep_int + 1) % 5def parent():"模拟服务器程序:监听管道"# print('Start parent')pipe_in_file = open(FIFO_PATH_STR, 'r')while True:# print('Hello')msg_str = pipe_in_file.readline()[:-1] # 数据发送完之前保持阻塞print('Parent {} got "{}" at {}'.format(os.getpid(), msg_str, time.time()))def main():if not os.path.exists(FIFO_PATH_STR):os.mkfifo(FIFO_PATH_STR) # 创建一个命名管道文件# print(sys.argv)if len(sys.argv) == 1:parent()else:child()if __name__ == '__main__':main()
输出:pipe_fifo.py(parent)
Parent 10202 got "Spam 0" at 1628746636.1461859
Parent 10202 got "Spam 1" at 1628746637.1473823
Parent 10202 got "Spam 2" at 1628746639.1496043
Parent 10202 got "Spam 3" at 1628746642.15281
Parent 10202 got "Spam 4" at 1628746646.1570377
Parent 10202 got "Spam 0" at 1628746646.1570966
Parent 10202 got "Spam 1" at 1628746647.1582468
Parent 10202 got "Spam 2" at 1628746649.1604269
Parent 10202 got "Spam 3" at 1628746652.1636524
Parent 10202 got "Spam 4" at 1628746656.1678512
Parent 10202 got "Spam 0" at 1628746656.1679099
命名管道用例
广泛适用于“客户端-服务器”模型。
初识套接字
套接字由Python的socket
模块实现,是比我们了解到的管道更为泛化的IPC手段。它可让数据传输在同一台计算机的不同程序间进行,也可以在远程联网的机器上的程序间进行。
套接字基本操作
套接字简而言之:
- 和FIFO的相同之处在于,套接字是机器水平的全局对象,它们不要求线程或进程间共享内存,因此对独立程序也适用。
- 与FIFO的不同之处在于,套接字根据端口号进行识别,而非文件系统的路径名称;它们利用一种大不相同的非文件API,虽然也可以封装进一个类文件对象;另外,它们的跨平台可移植性更好:它们几乎可以在所有Python上工作,包括Windows Pythond的标准版本。
此外,套接字还支持超出IPC及本章内容范围之外的网络功能。
下面的示例在并行运行的线程中启动了1个服务器和5个客户端,它们通过套接字通信。
示例:socket_preview.py
#!/usr/bin/env python
"""
套接字用于跨任务通信,它传输字节字符串,后者可以是pickle后的对象或编码后的Unicode文本
"""from socket import socket, AF_INET, SOCK_STREAM # 可移植的套接字API
import timePORT_INT = 50008
HOST = 'localhost'def server():"服务器线程"sock = socket(AF_INET, SOCK_STREAM) # tcp连接的ip地址sock.bind((HOST, PORT_INT)) # 绑定到这台机器的端口上sock.listen(5) # 最多允许5个等待中的客户端while True:connection, address = sock.accept() # 等待客户端连接data_bytes = connection.recv(1024) # 从这个客户端读取字节数据reply_str = 'Server got: [{}]'.format(data_bytes) # connection是一个新连接上的套接字connection.send(reply_str.encode()) # 将字节化的回复发给客户端def client(name_str):"客户端进程"time.sleep(float(name_str[-1]))sock = socket(AF_INET, SOCK_STREAM)sock.connect((HOST, PORT_INT)) # 连接到一个套接字端口sock.send(name_str.encode()) # 向监听者发送字节数据reply_bytes = sock.recv(1024) # 从监听者那里接受字节数据,信息最多包含1024字节sock.close()print('Client got: [{}]'.format(reply_bytes))def main():from threading import ThreadThread(target=server, daemon=True).start() # 不等待服务器进程(守护进程)for i_id_int in range(5):Thread(target=client, args=('client {}'.format(i_id_int),)).start() # 等待子进程结束if __name__ == '__main__':main()
输出:socket_preview.py
Client got: [b"Server got: [b'client 0']"]
Client got: [b"Server got: [b'client 1']"]
Client got: [b"Server got: [b'client 2']"]
Client got: [b"Server got: [b'client 3']"]
Client got: [b"Server got: [b'client 4']"]
套接字和独立程序
套接字更倾向于在单独的进程中和独立启动的程序间的IPC中起作用。
示例:socket_preview_progs.py
#!/usr/bin/env python
"测试套接字(独立启动程序)"from socket_preview import server, client # 二者使用相同的端口号
import sys
import os
from threading import Threaddef main():mode = sys.argv[1]if mode == 'server': # 在这个进程中运行服务器server()elif mode == 'client': # 在这个进程中运行客户端client('Client: process={}'.format(os.getpid()))else: # 在这个进程中运行5个客户端线程for i_id_int in range(5):Thread(target=client, args=('Client: process={} thread={}'.format(os.getpid(), i_id_int),)).start()if __name__ == '__main__':main()
输出:socket_preview_progs.py
$ ./socket_preview_progs.py client
Client got: [b"Server got: [b'Client: process=5763']"]
$ ./socket_preview_progs.py 5
Client got: [b"Server got: [b'Client: process=5837 thread=0']"]
Client got: [b"Server got: [b'Client: process=5837 thread=1']"]
Client got: [b"Server got: [b'Client: process=5837 thread=2']"]
Client got: [b"Server got: [b'Client: process=5837 thread=3']"]
Client got: [b"Server got: [b'Client: process=5837 thread=4']"]
套接字用例
本节的示例展示了套接字的基本IPC作用,但这不过是它的冰山一角。
- 套接字还可用于传输任意Python对象。
- 套接字还可用于将一个简单脚本的打印输出重定向到一个GUI窗口。
- 从Web上获取任意文本的程序可能是通过读取套接字传输的字节字符串来运行的。
- 事实上,可将整个互联网看成一个套接字用例。
加上程序需要交换数据的任何情境,套接字就成了普适、可移植的灵活工具。
信号
信号就像手拿一节竹竿戳戳某个进程。程序生成信号以触发另一进程中该信号的处理器,可能结束某个程序。如果这么听起来向Python里的抛出异常,那么正应如此。
为了在脚本中发送信号,Python提供一个signal
模块,来允许Python程序将Python函数登记为信号事件处理器。
示例:signal_1.py
#!/usr/bin/env python
"测试信号"import sys
import signal
import timedef now():"返回当前时间字符串"return time.ctime(time.time())def on_signal(sign_num_int, stack_frame):"信号[sign_num_int]的信号事件处理器"# print(stack_frame)print('Got signal {} at {}'.format(sign_num_int, now())) # 多数信号事件处理器一直有效def main():sign_num_int = sys.argv[1]signal.signal(sign_num_int, on_signal) # 布置信号处理器while True:"等待信号"signal.pause() # 或passif __name__ == '__main__':main()
输出:signal_1.py
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming-
--markdown-notes/my_PP4E/system$ ./signal_1.py 6 &
[6] 11391
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming-
--markdown-notes/my_PP4E/system$ kill -6 11391
Got signal 6 at Thu Aug 12 21:35:47 2021
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming-
--markdown-notes/my_PP4E/system$ kill -6 11391
Got signal 6 at Thu Aug 12 21:35:50 2021
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming-
--markdown-notes/my_PP4E/system$ kill -9 11391
- 信号范围为1~64
signal.signal
:接受信号编号和函数对象,布置该函数为此信号编号抛出时的处理器。除了SIGCHLD
这个除外,信号处理器在显式重置(比如将处理器设置为SIG_DEL
以回复默认行为,或者设置为SIG_IGN
以忽略信号)之前一直保持设置状态。SIGCHLD
的行为是平台各异的。signal.pause
:使进程休眠,直到捕捉到下一个信号。调用time.sleep
也可以达到这个目的,不过在我的Linux机器上不能和信号合用,它会生成一个被打断的系统调用错误。也可换为pass
,但这样太滥用系统资源了。- shell命令
kill
接受一个信号编号和进程ID;每用一个新的kill
命令发送一次信号,进程以Python信号处理器函数生成的信息作答。信号9总是完全中断进程。
signal.alarm
函数已在数秒后产生一个SIGALRM
信号。
示例:signal_2.py
#!/usr/bin/env python
"测试signal.alarm"import sys
import signal
import timedef on_signal(sign_num_int, stackframe):"信号处理器"print('Got alarm {} at {}'.format(sign_num_int, time.asctime()))def main():while True:signal.signal(signal.SIGALRM, on_signal)print('Setting at', time.asctime())signal.alarm(5) # 5秒后发送信号signal.pause()if __name__ == '__main__':main()
输出:signal_2.py
Setting at Thu Aug 12 21:58:09 2021
Got alarm 14 at Thu Aug 12 21:58:14 2021
Setting at Thu Aug 12 21:58:14 2021
Got alarm 14 at Thu Aug 12 21:58:19 2021
Setting at Thu Aug 12 21:58:19 2021
Got alarm 14 at Thu Aug 12 21:58:24 2021
Setting at Thu Aug 12 21:58:24 2021
...省略...
一般来说,信号必须避免有明显的使用痕迹,就像之前的time.sleep
所引发的错误一样。在多线程中,只有主线程能够设置信号处理器并对信号进行应答。
不过,只要使用得当,信号提供一个基于事件的通信机制。信号有时与其他IPC工具合用。例如,某个初始信号可能通知程序,有个客户端希望通过一个命名管道进行通信。就好像拍拍某人的肩膀来引起他的注意,然后再说话。
关于在类Unix平台上向Python脚本内已知的进程发送信号,还可以参考os.kill(pid, sig)
调用。
———————————————————————————————————————————
😃 学完博客后,是不是有所启发呢?如果对此还有疑问,欢迎在评论区留言哦。
如果还想了解更多的信息,欢迎大佬们关注我哦,也可以查看我的个人博客网站BeacherHou。