昨天讲到了Python为了弥补GIL造成的无法充分利用多核电脑增强运算能力的bug,引入了multprocessing模块,通过该模块内的Process类的实例化过程进行进行创建新的进程,从而越过了GIL(准确的说是建立了多个GIL)进行运算,使在进行附在运算的时候能够更为充分运用计算机的多核处理器。
多进程的运算其实就是一个并发的过程,因为实现了并发,多个进程之间在计算机的内存上是拥有各自独立的内存空间的,所以前面学习的多线程的通信queue就没有办法进行使用了,多进程之间的内存空间是独立的,所以今天就引入了Queue类、Pipe类、Manager类,这三个类是multprocessing模块下的用作类之间通信的,特别注意的是,虽然这三个类可以实现多个模块之间的通信,但是在创建多进程的过程中依然要提前将创建好的实例化对象导入到你想进行通信的进程中。
逐个介绍一下这三个类的具体使用方法吧,严格意义上说,Queue类和Pipe类只是实现了进程之间的数据交互,并没有实际的让数据进行共享,先进行介绍吧。
一、Queue类(用在多进程中的队列)
from multiprocessing import Process,Queue def test(q , n): q.put(n*n+1) print('son process',id(q)) if __name__ == '__main__': q = Queue() print('main process',id(q)) for i in range(3): p = Process(target = test , args = (q , i)) p.start() print(q.get()) print(q.get()) print(q.get())
输出的结果如下:
main process 2499803148240
son process 2209528037896
1
son process 2479661400528
2
son process 1666829795792
5
上面这个结果可以看出两个方面的结论:
1.多个进程内的q并不是同一个q,我们通过打印主进程和子进程各自的q的id,我们可以看到他们的id不同,可以说明进程之间的通信都是将数据进行复制的,所以多进程之
间的数据通信是一个非常耗费资源的过程,创建新的进程真的要非常慎重,而且据说像我这种渣渣程序员是遇不到多进程的情况的。
2.上面的两个进程,一个是test函数的进程,另一个是系统的主进程,通过在子进程中添加元素,而在主进程中取出元素,这就是一个简单的进程之间的数据交互。
二、Pipe(管道)
为啥子他的名字叫管道,其实Pipe就类似一个水管,两个开口,使两个进程之间完成数据交互,有点类似于之前学的socket中的accept之后建立了联系之后,我们使用conn进行信息的recv和send,这里建立Pipe之后也是生成两个参数,我们用两个参数在两个进程之间进行数据的传输。(说到了socket,今天打算在篇幅的最后复习一下socket的大致写法)
请看实例:
from multiprocessing import Process,Pipe def test(conn): conn.send([18, {'name' : 'xiaoyao'} , 'hello']) response = conn.recv() print('response is :' , response) conn.close() print('q_ID2:' , id(conn)) if __name__ == '__main__': parent_pipe , daughter_pipe = Pipe() print('q_ID1:',id(parent_pipe)) p = Process(target = test , args =(daughter_pipe,)) p.start() print(parent_pipe.recv()) parent_pipe.send('闺女,叫爸爸!!!') #准确的说,想娃了 p.join()
输出结果如下:
q_ID1: 1948134526312
[18, {'name': 'xiaoyao'}, 'hello']
response is : 闺女,叫爸爸!!!
q_ID2: 2499559056664
可以清楚的看到当print(parent_pipe.recv())的时候,我们通过daughter_pipe发送过来的列表已经被主进程收到了,同时p.join()之后,daughter_pipe打印出了response is : 闺女,叫爸爸!!!,所以上面就实现了两个进程之间的信息交互,确实有点像socket。
但是总的来说上面两个都是信息的交互,并没有进行实际的信息共享。
下面就是最终要的Manager
三、Manager
这个中文翻译确实不知道叫啥好,之前我们在进行列表、元组、字典等等数据类型的建立的时候都是直接建立,为了进行Manager的数据共享,我们就得变一下现有的变量建立方式,比如列表: l = manager.list(range(5))
下面先看一下实际的例子吧!
from multiprocessing import Process,Manager def test(dict_t,list_t,num): dict_t[num] = '1' dict_t['2'] = 2 dict_t[0.25] = None list_t.append(num) print('son process:', id(dict_t) , id(list_t)) if __name__ == '__main__': with Manager() as manager: dict_t = manager.dict() list_t = manager.list(range(5)) p_list = [] for i in range(10): p = Process(target = test , args = (dict_t,list_t,i,)) p.start() p_list.append(p) for res in p_list: res.join() print(dict_t) print(list_t)
输出结果:
{0: '1', 0.25: None, 2: '1', 3: '1', 4: '1', 5: '1', 6: '1', 1: '1', '2': 2, 9: '1', 7: '1', 8: '1'}
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
很明显,主进程创建了一个列表和一个字典,然后子进程对列表和字典进行编辑,最后又由主进程打印出经过子进程编辑过的字典和列表。
上面应该比较注意的是Manager的使用方法,上面先是用了类似文件的打开方式,with Manager() as manager: ,然后我们通过manager进行创建列表和元组。
然后其他还有各式各样的数据类型也是参考这个形式进行创建。引用一段英文,虽然看的不是很懂,但是大致就是这个意思啦。
Queue和pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据。
A manager object returned by Manager()
controls a server process which holds Python objects and allows other processes to manipulate them using proxies.
A manager returned by Manager()
will support types list
, dict
, Namespace
, Lock
, RLock
, Semaphore
, BoundedSemaphore
, Condition
, Event
, Barrier
, Queue
, Value
and Array
.
上面就是今天关于多进程之间通信的问题,下面就开始socket的简单复习,感觉自己再不回头看,估计就该把之前的东西忘光了。
四、socket模块
1.tcp协议的服务端和客户端的编写:
import socket #客户端 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.1.11',8000)) #拨通电话 phone.send('hello'.encode('utf-8')) #发消息 data=phone.recv(1024) print('收到服务端的发来的消息:',data) #服务端 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机 phone.bind(('127.0.1.11',8000)) #绑定手机卡 phone.listen(5) #开机 print('---->') conn,addr=phone.accept() #等电话 msg=conn.recv(1024) #收消息 print('客户端发来的消息是: ',msg) conn.send(msg.upper())#发消息 conn.close() phone.close()
2.udp套接字
import socket #客户端 ip_port=('127.0.0.1',8080) buffer_size=1024 while True: tcp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) msg = input('>>>:') tcp_client.sendto(msg.encode('utf-8'),ip_port) data,addr = tcp_client.recvfrom(buffer_size) print(data.decode('utf-8')) #服务端 # 可以将内部可能出现的参数在建立服务前进行设置,便于后期的修改 ip_port=('127.0.0.1',8080) back_log=5 buffer_size=1024 # 现在我们来建立一个udp协议的服务器 tcp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) tcp_server.bind(ip_port) # 基于udp协议的是不需要进行listen设置的 # 同样也不需要经过“三次握手”建立双向连接的需要,所以不需要accept while True: data,addr = tcp_server.recvfrom(buffer_size) #收到消息也是获得两个信息,一个是信息,另一个是对方地址 print(data.decode('utf-8')) print(addr) tcp_server.sendto(data.upper(),addr) # 发送内容的时候是需要写地址的 tcp_server.close()
简单的回头看一下,不然真的都不知道还有这么个功能了,所以,后面周末我就少看点新内容,多拿旧的内容好好的练一下收,突然发现自己写的计算器都没有保存。
今天的内容就是这些,结束的好早,明天好好办案,不能影响我的主业,“纪检监察干部”还是很屌的,哈哈哈(*/ω\*)
不吹牛逼,早点睡觉。