网络编程(五)
网络编程(五)
昨日回顾
创建进程
from multiprocessing import Process
if __name__ == '__main__':
p = Process(target=需要启动的进程, args=('参数',)) # 创建一个进程对象
p.start() # 告诉操作系统创建一个新的进程并启动进程中的所有功能
print('主进程')
或者重写__init__在创建对象前写入参数

TCP服务端并发
'''服务端'''
import socket
from multiprocessing import Process
def get_server():
server = socket.socket() # 创建一个服务器对象
from socket import SOL_SOCKET, SO_REUSEADDR
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 保证重复启动地址号不被占用
server.bind(('127.0.0.1', 8888)) # 写入地址和端口号
server.listen(5) # 最大5人等待
return server
def talk(sock): # 接受信息写成一个函数
while True:
data = sock.recv(1024)
sock.send(data.upper())
if __name__ == '__main__':
while True:
sock, addr = get_server().accept() # 启动服务器函数
p = Process(target=talk, args=(sock, ))
p.start() # 启动进程接受消息
join方法
类似将并发运行变为串行,主进程代码等待子进程代码运行结束之后再往后执行
进程.join() # 结束一个工作进程
进程间数据隔离
同一台计算机上的多个应用程序,默认情况下,数据相互间隔
进程对象属性和方法
1.获取进程号
current_process().pid
os.getpid()
os.getppid()
2.杀死子进程
terminate()
3.判断进程是否存活
is_alive()
僵尸进程与孤儿进程
僵尸进程
进程结束之后不会立刻清空所有信息,会短时间保留进程号等信息等待父进程回收,所有的进程都会经历僵尸进程
父进程回收子进程资源的两种方式
1.父进程正常结束
2.调用join方法
孤儿进程
子进程正常运行 父进程意外死亡(主动杀死>>> taskkill\kill)
守护进程
守护进程会跟随主进程一起死亡
互斥锁
在对数据进行操作的时候,并发很容易造成数据混乱
互斥锁的本质就是将并发转换为串行
mutex = Lock()
mutex.acquire() # 抢锁
mutex.release() # 放锁
今日学习内容
![]()
消息队列
队列:先进先出(使用频率很高)
堆栈:先进后出(特定常见下用)
from multiprocessing import Queue
q = Queue(5) # 括号内参数表示最大队列数
# 朝队列中存放数据
q.put(111)
q.put(222)
q.put(333)
print(q.full()) # False 判断队列是否满了
q.put(444)
q.put(555)
print(q.full()) # True
# q.put(666) # 超出最大长度 原地阻塞等待队列中出现空位
print(q.get())
print(q.get())
print(q.empty()) # False 判断队列是否空了
print(q.get())
print(q.get())
print(q.get())
print(q.empty()) # True
# print(q.get()) # 队列中没有值 继续获取则阻塞等待队列中给值
print(q.get_nowait()) # 队列中如果没有值 直接报错
q.full() 判断队列是否满了
q.empty() 判断队列是否空了
q.get_nowait() 队列中没有值就报错
IPC机制(进程间通信)
1.主进程与子进程数据交互
2.两个子进程数据交互
本质:不同内存空间中的进程数据交互
注释:
from multiprocessing import Process, Queue
def producer(q): # 生产函数
print('子进程producer从队列中取值>>>:', q.get())
q.put('子进程producer往队列中添加值')
def consumer(q): # 消费者函数
print('子进程consumer从队列中取值>>>:', q.get())
if __name__ == '__main__':
q = Queue() # 启用一个队列
p = Process(target=producer, args=(q, )) # 启动一个生产进程
p1 = Process(target=consumer, args=(q,)) # 启动一个消费函数
p.start() # 启动生产函数
p1.start() # 启动消费函数
q.put(0) # 主进程往队列中存放数据123
print('主进程')
生产函数启动进程后往主进程添加数值,消费函数启动进程后往主进程拿数值
生产者消费者模型
生产者:产生数据,制作数据(类似商店上货)
消费者:拿走数据,消耗数据(类似顾客购物)
在爬虫领域:
通过代码爬取网页数据,获取数据即可称为生产者
针对代码数据做筛选处理被称之为消费者
如果使用进程来演示
需要一个媒介(消息队列)Queue
from multiprocessing import Process, Queue, JoinableQueue
import time
import random
def producer(name, food, q):
for i in range(5):
data = f'{name}生产了{food}{i}'
print(data)
time.sleep(random.randint(1, 3)) # 模拟产生过程
q.put(data)
def consumer(name, q):
while True:
food = q.get()
time.sleep(random.random())
print(f'{name}购买了{food}')
q.task_done() # 每次去完数据必须给队列一个反馈
if __name__ == '__main__':
q = JoinableQueue()
p1 = Process(target=producer, args=('上货员1', '泡面', q))
p2 = Process(target=producer, args=('上货员2', '挂面', q))
c1 = Process(target=consumer, args=('顾客1', q))
c2 = Process(target=consumer, args=('顾客2', q))
c1.daemon = True
c2.daemon = True
p1.start()
p2.start()
c1.start()
c2.start()
# 生产者生产完所有数据之后 往队列中添加结束的信号
p1.join()
p2.join()
# q.put(None) # 结束信号的个数要跟消费者个数一致才可以
"""队列中其实已经自己加了锁 所以多进程取值也不会冲突 并且取走了就没了"""
q.join() # 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确)
"""执行完上述的join方法表示消费者也已经消费完数据了"""
线程理论
进程简介:创建一块空间,内部产生代码与数据
线程概念:线程相当于在进程中正在调用运转代码的抽象概念
一个进程中至少有一个线程
进程仅仅是一块内存空间和当中的数据体,真正被运行的其实是线程
启动一个线程的消耗非常的小
启动一个进程:1.申请内存空间 2.拷贝代码
启动一个线程:一个进程的线程都是共用数据的,开启进程后可创建多条线程
多个功能所需求的代码如果相同,那么他们所开设的应该是多线程而不是多进程
开设线程的两种方式
"进程与线程的代码实操几乎是一样的"
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
# 创建线程无需在__main__下面编写 但是为了统一 还是习惯在子代码中写
t = Thread(target=task, args=('jason', ))
t.start() # 创建线程的开销极小 几乎是一瞬间就可以创建
print('主线程')
class MyThread(Thread):
def __init__(self, username):
super().__init__()
self.username = username
def run(self):
print(f'{self.username} jason is running')
time.sleep(3)
print(f'{self.username} is over')
t = MyThread('jasonNB')
t.start()
print('主线程')
线程实现TCP服务端的并发
仔细体会开设进程和线程的本质区别
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
while True:
sock, addr = server.accept()
# 每类一个客户端就创建一个线程做数据交互
t = Thread(target=talk, args=(sock,))
t.start()
线程的运行资源消耗极小,在客户端需要调用相同的服务端代码时,效果更好。
线程join方法与守护线程
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
t = Thread(target=task, args=('jason', ))
t.start()
t.join() # 主线程代码等待子线程代码运行完毕之后再往下执行
print('主线程')
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
t1 = Thread(target=task, args=('eason',))
t2 = Thread(target=task, args=('kevin',))
t1.daemon = True
t1.start()
t2.start()
print('主线程')
结果,主线程会等待所有的子线程结束才会结束整个进程,因为子线程所使用的的代码是从相同的进程中取出的,如果主线程结束时直接关闭进程,那么会导致子进程丢失所需要的代码。
而守护进程也是同理,所有线程使用的都是相同的进程代码,只有所有线程都运行完后,进程才会真正的被关闭。
线程对象属性和方法
验证一个进程下多个线程是否确实处于同一进程
验证方式:
统计进程下活跃的线程数
active_count() # 主线程同样算
获取线程的名字
1.current_thread().name
MainThread 主线程
Thread-1、Thread-2 子线程
2.self.name
GIL全局解释器锁
1. GIL概念:
GIL,全局解释器锁(global interpreter lock),它不是python语言的特性,而是python默认的解析器cpython的特性cpython要求每个线程必须先获取GIL锁,才能执行线程中的代码
目的:解决多线程同时竞争解析器程序的全局变量而出现的线程安全问题
不足:在多线程中不能充分利用多核cpu 原因是一个进程只存在一把gil锁,当在执行多个线程时,内部会争抢gil锁,这会造成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源
2. 单线程、多线程、多进程执行分析
- 单线程: 双核cpu使用率50%
- 多线程:双核cpu使用率50%
- 多进程:双核cpu使用率100%
3.如何解决GIL问题:
- 换语言, 在处理多线程代码时,用其他语言代码,比如用
c或者java
- 换解析器,比如换jpython
- 业界常用的方案: 多进程+多协程方法
4. 面试题:
描述Python GIL的概念, 以及它对python多线程的影响?一个单线程抓取网页的程序,与一个多线程抓取网页的程序哪个性能更高,并解释原因
1. GIL,全局解释器锁(global interpreter lock),它是cpython解析器的特性,不是python的特性 ,它要求线程在执行前,需要获取GIL锁,
2. 由于GIL的存在,会影响多线程不能利用多核CPU资源(原因是一个进程只存在一把gil锁,当在执行多个线程时,内部会争抢gil锁,这会造成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源),通过多进程方式可利用多个CPU资源
3. 线程释放GIL锁的情况:
1.在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL
2.Python 3x使用计时器(执行时间达到阈值后,当前线程释放GIL)
4. 多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁,这样在线程阻塞情况下,可以执行其他线程中的代码
多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁
今日小结
![]()
学习使我充实。

浙公网安备 33010602011771号