服务端的并发处理-多线程多进程
在服务端进行挂起监听的时候,可能会遇到同时大量的用户进行连接和数据请求,那么单进程的可以使用多路复用的方式进行解决这个问题,这个技术后面再讲,现在最简单的解决方案就是使用多线程和多进程
多线程与多进程的选择这取决于到底是IO密集型还是CPU密集型,IO密集型需要使用多线程,cpu密集型进行多进程
这里显然就是io密集型的,但是为了练习,现在使用多线程和多进程都实验一把
from socket import *
from multiprocessing import Process
def main():
# 创建对象
server = socket(AF_INET, SOCK_STREAM)
# 配置断开释放端口
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
# 挂起服务器
server.bind(('', 8888))
# 设置成监听模式
server.listen()
while True:
# 创建新的连接对象
new_server, client_info = server.accept()
# 创建新的进程
print(f'用户{client_info}已经连接')
p = Process(target=get_data, args=(new_server,client_info))
# 启动进程
p.start()
# 因为内存对应的new_server存在两个指向,所以外面的必须关闭,否则无法释放掉对象
new_server.close()
def get_data(new_server,client_info):
# 拿出数据
data = new_server.recv(1024)
while data:
# 打印数据
print(f'来自{client_info}的数据{data}')
# 如果一次没有取完,继续取
data = new_server.recv(1024)
# 客户端断开连接后进行关闭处理
new_server.close()
if __name__ == '__main__':
main()
多进程与多线程的区别在于是否进行对象关闭
from socket import *
from threading import Thread
def main():
# 创建对象
server = socket(AF_INET, SOCK_STREAM)
# 配置断开释放端口
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
# 挂起服务器
server.bind(('', 8888))
# 设置成监听模式
server.listen()
while True:
# 创建新的连接对象
new_server, client_info = server.accept()
# 创建新的进程
print(f'用户{client_info}已经连接')
t = Thread(target=get_data, args=(new_server,client_info))
# 启动线程
t.start()
# 因为多线程并不会复制进程的内存,所以new_server的指向不会出现两个
def get_data(new_server,client_info):
# 拿出数据
data = new_server.recv(1024)
while data:
# 打印数据
print(f'来自{client_info}的数据{data}')
# 如果一次没有取完,继续取
data = new_server.recv(1024)
# 客户端断开连接后进行关闭处理
new_server.close()
if __name__ == '__main__':
main()
多进程的内存复制实质
笼统的认为多进程在开新进程的时候,子进程会复制父进程的内存空间,从而具备整整个父进程所具有的变量
这只是非常笼统的认为,实际上计算机内存的存储方式分为两种一种就是栈,一种是堆
在计算中,如果内存地址中的数据没有变量指向它,则认为整个数据已经不需要了,系统就会对这个数据进行清空以释放内存
实际在我们父进程中创建的对象new_server已经被存在内存里面,父进程保存的是一个指向这个数据的映射关系,那么父进程在产生子进程的时候,子进程会复制父进程的这些映射关系,此时new_server对象映射的ip就有两个变量指向这个内存空间,从而使得内存中的这个数据ip就会受到两个指向
上面的进行多进程中,进程函数确实执行完成之后清空的子进程对这个数据ip的指向,子进程的映射关系呗删除,然而这个数据ip还存在一个父进程的变量进行映射,系统发现内存中这个数据ip居然还有一个指向,因此不会进行删除释放内存
那么回头看代码,在父进程分裂出子进程后直接继续执行,父进程马上就对自己的映射关系进行删除,此时整个内存里面的数据ip不会消失,因为子进程中还有对这个数据ip的指向
如果子进程也删除了自己的指向,系统就会知道这个数据ip已经没有任何指向了,那么系统就会清空这个数据ip,从而彻底结束这个new_server.
栈是一种先进后出的数据存储模式,我们大部分变量名就存在这里面,其优势在于非常快,然而变量名并不是变量值,栈中存储的都是一些映射方式
堆则不同,堆用来存大量的复杂数据,其容量大,但是速度慢
比如如果存在
a = 1
b = 1
那么1这个数据就有两个变量进行指向,所以数据1的映射数为2,当我们执行a += 1,此时变量名a指向的是2,而2是一个新的数据,在内存中会分配一个新的地址,此时数据1的映射数就减1称为了1.系统之所以没有清空数据1占用的内存,因为b这个变量映射在数据1上面还有映射
敲黑板
如果此时对b进行 b += 1的操作,则此时就会执行,先将数据2上面增加一个映射关系,这个映射是b变量指向的,此时数据2存在的映射数为2,那么原来的数据1就会减少一个映射关系,此时的数据1上的映射数为0,系统此时发现数据1上已经没有映射了,那么就会将数据0占用的内存释放掉,那么ab两个变量再次指向的是同一片内存空间