05 网络编程和并发编程
网络编程
网络编程: 开发cs架构软件
并发编程: 数据库、前端、django,开发bs架构软件
socket套接字
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部让Socket去组织数据,以符合指定的协议。
其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。
网络编程TCP
socket最简单版本
"""
send与recv需对应,不能服务端和客户端同时都一样,否则会卡住
send是发送数据到内存,recv是跟内存要数据
"""
# 服务端
import socket
server = socket.socket() # 不传参数默认是TCP协议 拿手机
server.bind(('127.0.0.1', 8080)) # bind((host, port)),绑定ip和端口 插电话卡
server.listen(5) # 半连接池为5,1个客户端连接中,5个等待中,再来1个则报错 开机
conn, addr = server.accept() # addr为客户端的地址('127.0.0.1', 51323) 等别人给你打电话 阻塞
data = conn.recv(1024) # 接收1024字节数据,conn就是服务端和某一个客户端连接的双向通道 听别人说话 阻塞
print(data)
conn.send(b'hello baby~') # 对别人说话
conn.close() # 挂电话
server.close() # 关机
# 客户端
import socket
client = socket.socket() # 拿手机
client.connect(('127.0.0.1', 8080)) # 写的是服务端的ip和port 拨号
client.send(b'hello world!') # 对别人说话
data = client.recv(1024) # 听别人说话
print(data)
client.close() # 挂电话
解决粘包问题最复杂版本
粘包问题
# 粘包问题
# 服务端
"""
接收到的数据:
b'helloworldbaby'
b''
b''
"""
import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
conn, addr = server.accept()
data = conn.recv(1024)
print(data)
data = conn.recv(1024)
print(data)
data = conn.recv(1024)
print(data)
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
client.send(b'hello')
client.send(b'world')
client.send(b'baby')
struct模块
import struct
res = '1111222233334444555566667777888899990000asdfghjkl;'
print('原始数据长度:',len(res)) # 50
res1 = struct.pack('i',len(res))
print("i模式报头长度为4:",len(res1)) # 4
res2 = struct.pack('q',len(res))
print("当数据特别大i模式打包不了可以用q模式,q模式报头长度为8:",len(res2)) # 8
res3 = struct.unpack('i',res1)[0]
res4 = struct.unpack('q',res2)[0]
print('i模式解包后数据长度:',res3) # 50
print('i模式解包后数据长度:',res4) # 50
struct模块解决粘包问题
"""
解决粘包问题:
服务端:
1.制作包含客户端数据长度信息的字典
2.制作字典报头
3.发送字典报头
4.发送字典
5.发真实数据
客户端
1.接受字典报头
2.解析拿到字典长度
3.接受字典
4.从字典中获取真实数据长度
5.接收真实数据
不支持并发,即不能同时服务多个客户端,服务端服务一个客户端,其他不超过半连接池个数的客户端等待服务端断开连接后可通信,超过半连接池个数则报错
"""
# 服务端
import socket
import subprocess
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True: # 外层循环解决当前客户端断开后,服务端再去等待连接新的客户端
conn, addr = server.accept()
while True: # 内层循环解决一直接收客户端输入的命令并且返回执行结果,直到客户端断开连接
try:
cmd = conn.recv(1024)
if len(cmd) == 0:break # 解决mac与linux,客户端异常退出后,服务端不会报错,但会一直收b''
cmd = cmd.decode('utf-8')
obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
res = obj.stdout.read() + obj.stderr.read()
d = {'name':'jason','file_size':len(res),'info':'实现客户端发送命令给服务端执行,服务端将执行结果返回给客户端'} # 字典里保存数据长度和其他信息,解决struct模块打包数据大小有限
json_d = json.dumps(d)
# 1.先制作一个字典报头
header = struct.pack('i',len(json_d))
# 2.发送字典报头
conn.send(header)
# 3.发送字典
conn.send(json_d.encode('utf-8'))
# 4.再发真实数据
conn.send(res)
except ConnectionResetError: # 解决客户端异常退出后服务端报ConnectionResetError
break
conn.close() # 客户端退出后服务端关闭与客户端的连接
# 客户端
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
msg = input('cmd>>>:').encode('utf-8')
if len(msg) == 0:continue # 解决客户端发送空往下执行到recv,而服务端也在recv,则会一直停在recv卡住
client.send(msg)
# 1.先接收字典报头,i模式长度为4
header_dict = client.recv(4)
# 2.解析报头获取字典长度
dict_size = struct.unpack('i',header_dict)[0]
# 3.再接收字典数据
dict_bytes = client.recv(dict_size)
dict_data = json.loads(dict_bytes.decode('utf-8'))
# 4.再从字典中获取真实数据信息,接收真实数据
print(dict_data)
recv_size = 0
real_data = b''
while recv_size < dict_data.get('file_size'):
data = client.recv(1024)
real_data += data
recv_size += len(data)
print(real_data.decode('gbk'))
示例:客户端选择电影上传到服务端
# 客户端
import socket
import json
import os
import struct
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
# 获取电影列表,循环展示
MOVIE_DIR = r'E:\恢复的数据\python学习\python视频教程\day29\视频'
movie_list = os.listdir(MOVIE_DIR)
for i,movie in enumerate(movie_list,1):
print(i,movie)
# 用户选择
choice = input('please choice movie to upload>>> :')
# 判断是否是数字
if choice.isdigit():
# 将字符串数转为int
choice = int(choice) - 1
# 判断用户选择在不在列表范围内
if choice in range(len(movie_list)):
# 获取到用户选择的文件
path = movie_list[choice]
# 拼接文件的绝对路径
file_path = os.path.join(MOVIE_DIR,path)
# 获取文件大小
file_size = os.path.getsize(file_path)
# 定义一个字典
res_d = {
'file_name':path,
'file_size':file_size,
'msg':'上传电影'
}
# 序列化字典
json_d = json.dumps(res_d)
json_bytes = json_d.encode('utf-8')
# 1.先制作字典格式的报头
header = struct.pack('i',len(json_bytes))
# 2.发送字典的报头
client.send(header)
# 3.再发字典
client.send(json_bytes)
# 4.再发文件数据(打开文件循环发送)
with open(file_path,'rb') as f:
for line in f:
client.send(line)
else:
print('not in range')
else:
print('must be a number')
# 服务端
import socket
import os
import json
import struct
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn,addr = server.accept()
while True:
try:
header_len = conn.recv(4)
# 解析字典报头
header_len = struct.unpack('i',header_len)[0]
# 接收字典数据
header_dic = conn.recv(header_len)
real_dic = json.loads(header_dic.decode('utf-8'))
# 获取数据长度
total_size = real_dic.get('file_size')
# 循环接收并写入文件
recv_size = 0
with open(real_dic.get('file_name'),'wb') as f:
while recv_size < total_size:
data = conn.recv(1024)
f.write(data)
recv_size += len(data)
print('上传成功')
except ConnectionResetError as e:
print(e)
break
conn.close()
解决报错:OSError: [Errno 48] Address already in use
# 加入一条socket配置,重用ip和端口
from socket import SQL_SOCKET,SO_REUSEADDR
sk.setsockopt(SQL_SOCKET,SO_REUSEADDR,1) # 就是它,在bind前加
网络编程UDP
UDP基本使用
"""
UDP通信: 数据报协议(自带报头),没有双向通道,TCP类似于打电话,UDP类似于发短信
基于UDP协议传输数据是不安全的
1.udp协议客户端允许发空
2.udp协议不会粘包
3.udp协议服务端不存在时,客户端不会报错(sendto不会报错)
4.udp协议支持并发
并发:看起来像同时运行的
并行:真正意义上的同时运行
"""
# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM) # type=socket.SOCK_DGRAM:UDP协议
server.bind(('127.0.0.1',8080))
while True:
data, addr = server.recvfrom(1024)
print('客户端发的数据:',data)
print('客户端的地址:',addr)
server.sendto(data.upper(),addr)
# 客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)
while True:
msg = input(">>> ").encode('utf-8')
client.sendto(msg,server_address)
data, addr = client.recvfrom(1024)
print('服务端发的数据',data)
print('服务端的地址',addr)
socketserver模块
socketserver支持服务端并发效果
TCP
"""
可解决socket tcp不支持并发问题
"""
# 服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
while True:
data = self.request.recv(1024)
print(self.client_address) # 客户端地址
print(data.decode('utf-8'))
self.request.send(data.upper())
if __name__ == '__main__':
# 客户端来连接,会自动交给自定义类MyServer中的handle方法处理
server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer) # 创建一个基于TCP的对象
server.serve_forever() # 启动该服务对象
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True
client.send(b'hello')
data = client.recv(1024)
print(data.decode('utf-8'))
UDP
# 服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
while True:
data,sock = self.request
print(self.client_address) # 客户端地址
print(data.decode('utf-8'))
sock.sendto(data.upper(),self.client_address)
if __name__ == '__main__':
# 客户端来连接,会自动交给自定义类MyServer中的handle方法处理
server = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyServer) # 创建一个基于UDP的对象
server.serve_forever() # 启动该服务对象
# 客户端
import socket
import time
client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)
while True:
client.sendto(b'hello',server_address)
data,addr = client.recvfrom(1024)
print(data.decode('utf-8'),addr)
time.sleep(1)
并发编程
进程
进程
多道技术:
1.空间上的复用: 多个程序共用一套计算机硬件
2.时间上的复用: 切换+保存状态
当一个程序遇到IO操作,操作系统会剥夺该程序的cpu执行权限(提高了cpu的利用率,并且也不影响程序的执行效率)
当一个程序长时间占用cpu,操作系统也会剥夺该程序的cpu执行权限(降低了程序的执行效率)
进程调度:
先来先服务调度算法
短作业优先调度算法
时间片轮转法
多级反馈队列
并发:看起来像同时运行
并行:真正意义上的同时运行
单核的计算机不能实现并行,可以实现并发
同步异步:表示任务的提交方式
同步:任务提交之后,原地等待任务执行并拿到返回结果才走,期间不做任何事(程序层面的表现就是卡住了)
异步:任务提交之后,不再原地等待,而是继续执行下一行代码(结果通过其他方式获取)
阻塞非阻塞:表示程序的运行状态
阻塞:阻塞态
非阻塞:就绪态、运行态
创建进程
"""
windows创建进程会将代码以模块的方式从上往下执行一遍,一定要在if __name__ == '__main__':代码块内创建,否则报错
linux创建进程会直接将代码完整的拷贝一份
创建进程就是在内存中重新开辟一块内存空间,将允许产生的代码丢进去,一个进程对应内存就是一块独立的内存空间
进程与进程之间数据是隔离的,无法直接交互,可以通过某些技术实现间接交互
"""
# 方式1:Process实例化
"""
主
egon is running
egon is over
"""
from multiprocessing import Process
import time
def test(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
if __name__ == '__main__':
p = Process(target=test,args=('egon',)) # 创建一个进程对象
p.start() # 告诉操作系统创建一个进程,什么时候创由操作系统随机决定
print('主')
# 方式2:继承Process重写run方法
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
print('%s is running' % self.name)
time.sleep(3)
print('%s is over' % self.name)
if __name__ == '__main__':
p = MyProcess('egon')
p.start()
print('主')
join方法
from multiprocessing import Process
import time
def test(name,i):
print('%s is running'%name)
time.sleep(i)
print('%s is over'%name)
if __name__ == '__main__':
p1 = Process(target=test,args=('egon',3))
p2 = Process(target=test,args=('jason',1))
start_time = time.time()
p1.start()
p2.start()
p1.join() # 主进程等待某个子进程运行结束才运行,不会影响其他子进程的运行
p2.join()
print('主')
print(time.time() - start_time)
"""
egon is running
jason is running
jason is over
egon is over
主
3.1530721187591553
"""
进程对象及其他方法
"""
获取进程号:
方式1:current_process().pid 获取当前进程号
方式2:
os.getpid() 获取当前进程号
os.getppid() 获取当前进程的父进程的进程号
进程对象.terminate() 告诉操作系统杀死当前进程
进程对象.is_alive()) 判断进程是否存活
"""
from multiprocessing import Process,current_process # 获取当前进程号
import os
import time
def test(name):
print('%s is running'%name,'当前子进程:%s'%current_process().pid)
print('%s is running'%name,'当前子进程:%s'%os.getpid(),'父进程:%s'%os.getppid())
time.sleep(3)
print('%s is over'%name)
if __name__ == '__main__':
p = Process(target=test,args=('egon',))
p.start()
# p.terminate() # 杀死当前进程 其实是告诉操作系统帮你杀死一个进程
# time.sleep(0.1)
# print(p.is_alive()) # 判断进程是否存活
print('主','当前主进程:%s'%current_process().pid)
print('主','当前主进程:%s'%os.getpid(),'主主进程:%s'%os.getppid())
"""
主 当前主进程:21872
主 当前主进程:21872 主主进程:4396
egon is running 当前子进程:4496
egon is running 当前子进程:4496 父进程:21872
egon is over
"""
from multiprocessing import Process
import time
def test(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
if __name__ == '__main__':
p = Process(target=test,args=('egon',))
p.start()
time.sleep(1)
p.terminate() # 告诉操作系统杀死当前进程
time.sleep(0.1)
print(p.is_alive()) # 判断进程是否存活
print('主')
"""
egon is running
False
主
"""
僵尸进程和孤儿进程和守护进程
"""
父进程回收子进程资源的两种方式:
1.join方法
2.父进程正常死亡
僵尸进程:子进程结束但pid等还未被父进程回收,所有进程都会步入僵尸进程阶段
孤儿进程:子进程没死,父进程意外死亡,linux针对孤儿进程会有儿童福利院(init),父进程意外死亡则子进程会被福利院收养
守护进程:一个进程守护另一个进程,被守护的进程死了后守护进程立刻死
"""
from multiprocessing import Process
import time
def test(name):
print('%s子进程正常活着'%name)
time.sleep(3)
print('%s子进程正常死亡'%name)
if __name__ == '__main__':
p = Process(target=test,args=('egon',))
p.daemon = True # 将该子进程设置为主进程的守护进程,必须放在start语句之前,否则报错
p.start()
time.sleep(0.1)
print('主进程')
"""
egon子进程正常活着
主进程
"""
互斥锁
"""
当多个进程操作同一份数据时会造成数据的错乱,需加锁处理,将并发变成串行,虽然降低了效率但是提高了数据的安全
注意:
1.锁不要轻易使用,容易造成死锁现象
2.在处理数据的部分加锁,不要在全局加锁
3.锁必须在主进程中产生,交给子进程去使用
"""
from multiprocessing import Process,Lock
import time
import json
# 查票
def search(i):
with open('data','r',encoding='utf-8') as f:
data = f.read()
t_d = json.loads(data)
print('用户%s查询余票为:%s'%(i,t_d.get('ticket')))
# 买票
def buy(i):
with open('data','r',encoding='utf-8') as f:
data = f.read()
t_d = json.loads(data)
time.sleep(1)
if t_d.get('ticket') > 0:
# 票数减一
t_d['ticket'] -= 1
# 更新票数
with open('data','w',encoding='utf-8') as f:
json.dump(t_d,f)
print('用户%s抢票成功'%i)
else:
print('用户%s抢票失败,没票了'%i)
def run(i,mutex):
search(i)
mutex.acquire() # 抢锁,只要有人抢到了锁其他人就必须等待该人释放锁
buy(i)
mutex.release() # 释放锁
if __name__ == '__main__':
mutex = Lock() # 生成一把锁
for i in range(3):
p = Process(target=run,args=(i,mutex))
p.start()
"""
用户0查询余票为:1
用户1查询余票为:1
用户2查询余票为:1
用户0抢票成功
用户1抢票失败,没票了
用户2抢票失败,没票了
"""
进程间通信
进程间通信技术-队列
"""
队列:先进先出
堆栈:先进后出
full、get_nowait、empty 都不适用于多进程
"""
from multiprocessing import Queue
q = Queue(3) # 传参表示队列最大存储数,不传默认为2147483647
q.put(1) # 往队列中添加数据
q.put(2)
print(q.full()) # False 判断队列是否满了
q.put(3)
print(q.full()) # True
q.put(3) # 当队列满了再添加数据不会报错,会原地等待,直到有数据被取走(阻塞态)
print(q.get()) #从队列中取数据
print(q.get())
print(q.empty()) # False 判断队列数据是否取完
print(q.get())
print(q.empty()) # True
print(q.get_nowait()) # 取值,没有值不等待直接报错
print(q.get()) # 当队列数据被取完再次去取不会报错,会原地等待,直到有数据放入(阻塞态)
进程间通信IPC机制
# 子进程往队列放数据,主进程从队列取数据
from multiprocessing import Process,Queue
def producer(q):
q.put('hello')
if __name__ == '__main__':
q = Queue()
p = Process(target=producer,args=(q,))
p.start()
print(q.get())
# 两个子进程相互放取数据
from multiprocessing import Process,Queue
def producer(q):
q.put('hello')
def consumer(q):
print(q.get())
if __name__ == '__main__':
q = Queue()
p = Process(target=producer,args=(q,))
c = Process(target=consumer, args=(q,))
p.start()
c.start()
生产者消费者模型
from multiprocessing import Process,Queue
import random
import time
def producer(name,food,q):
for i in range(3):
data = '%s生产的%s %s'%(name,food,i)
time.sleep(random.random())
q.put(data)
print(data)
def consumer(name,q):
while True:
data = q.get()
if data == None:break # 当消费者从队列中取到None后结束进程
print('%s吃了%s'%(name,data))
time.sleep(random.random())
if __name__ == '__main__':
q = Queue()
p = Process(target=producer,args=('egon','馒头',q))
p1 = Process(target=producer,args=('tank','生蚝',q))
c = Process(target=consumer,args=('jason',q))
c1 = Process(target=consumer,args=('rose',q))
p.start()
p1.start()
c.start()
c1.start()
p.join()
p1.join()
q.put(None) # 当生产者进程都结束后,主进程往队列添加None,有几个消费者就得添加几个None(目的是为了解决当生产者生产完并且消费者也消费完后,结束消费者进程而不是从队列get夯在那)
q.put(None)
"""
egon生产的馒头 0
jason吃了egon生产的馒头 0
tank生产的生蚝 0
rose吃了tank生产的生蚝 0
egon生产的馒头 1
rose吃了egon生产的馒头 1
tank生产的生蚝 1
jason吃了tank生产的生蚝 1
egon生产的馒头 2
rose吃了egon生产的馒头 2
tank生产的生蚝 2
jason吃了tank生产的生蚝 2
"""
# JoinableQueue可解决当有多个消费者需要往队列添加多个None来结束消费者
from multiprocessing import Process,JoinableQueue
import random
import time
def producer(name,food,q):
for i in range(3):
data = '%s生产了%s%s'%(name,food,i)
time.sleep(random.random())
q.put(data)
print(data)
def consumer(name,q):
while True:
data = q.get()
if data == None:break
print('%s吃了%s'%(name,data))
time.sleep(random.random())
q.task_done() # 告诉队列已从队列中取出一个数据并且处理完毕
if __name__ == '__main__':
q = JoinableQueue()
p = Process(target=producer,args=('大厨egon','馒头',q))
p1 = Process(target=producer,args=('跟班tank','生蚝',q))
c = Process(target=consumer,args=('许兆龙',q))
c1 = Process(target=consumer,args=('吃货jerry',q))
p.start()
p1.start()
c.daemon = True # 将所以消费者进程配为主进程的守护进程,因为主进程结束代表q.join()结束代表队列中数据全部已取出
c1.daemon = True
c.start()
c1.start()
p.join()
p1.join()
q.join() # 等待队列中数据全部取出
线程
线程
进程:资源单位
线程:执行单位
将内存比成工厂,那么进程就是工厂里的车间,线程就是车间里的流水线
每个进程都自带一个线程,线程才是真正的执行单位,进程只是在线程运行过程中提供代码运行所需要的资源
为什么要有线程:
开进程:
1.申请内存空间,耗资源
2."拷贝代码",耗资源
开线程:一个进程内可以起多个线程,并且线程与线程之间数据是共享的,开启线程的开销要远远小于开启进程的开销
创建线程
# 方式1:Thread实例化
"""
egon is running
主
egon is over
"""
from threading import Thread
import time
def task(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
t = Thread(target=task,args=('egon',)) # 创建一个线程对象,开线程不需要在__main__代码块内,但习惯性还是写在__main__代码块内
t.start() # 告诉操作系统创建一个线程,什么时候创由操作系统随机决定
print('主')
# 方式2:继承Thread重写run方法
from threading import Thread
import time
class MyThread(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
print('%s is running'%self.name)
time.sleep(3)
print('%s is over'%self.name)
t = MyThread('egon')
t.start()
print('主')
线程对象及其他方法
# current_thread().name 获取当前线程名
# active_count() 获取当前活跃的线程数
# 线程对象.join 主线程等待某个子线程运行结束才运行,不影响其他子线程的运行
from threading import Thread,current_thread,active_count
import time
import os
def task(name,i):
print('%s is running'%name,'线程名:',current_thread().name)
print('当前线程所在进程号',os.getpid())
time.sleep(i)
print('%s is over'%name)
t = Thread(target=task,args=('egon',1))
t1 = Thread(target=task,args=('jason',2))
t.start()
t1.start()
t1.join()
print('当前正在活跃的线程数',active_count())
print('主')
print('主线程名',current_thread().name)
print('主线程所在进程号',os.getpid())
"""
egon is running 线程名: Thread-1
当前线程所在进程号 10112
jason is running 线程名: Thread-2
当前线程所在进程号 10112
egon is over
jason is over
当前正在活跃的线程数 1
主
主线程名 MainThread
主线程所在进程号 10112
"""
守护线程
"""
主线程结束也就意味着进程结束
主线程会等待其他非守护线程结束才结束(因为子线程在运行时需要使用进程中的资源,而主线程一旦结束进程也就结束进程资源也就销毁了)
"""
from threading import Thread,current_thread
import time
def task(i):
print(current_thread().name)
time.sleep(i)
print('over')
t = Thread(target=task,args=(1,))
t.daemon = True # 将该子线程设置为主进程的守护线程,必须放在start语句之前,否则报错
t.start()
print('主')
"""
Thread-1
主
"""
from threading import Thread
import time
def foo():
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("main")
"""
123
456
main
end123 # end123会打印是因为,虽然t1是守护线程,但主线程需要等所以其他非守护线程结束才结束
end456
"""
线程间通信
# 线程与线程之间数据是共享的
from threading import Thread
money = 666
def task():
global money
money = 999
t = Thread(target=task)
t.start()
t.join()
print(money) # 999
互斥锁
from threading import Thread,Lock
import time
n = 100
def task():
global n
tmp = n
time.sleep(0.1)
n = tmp - 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(n) # 99
# 解决多个线程处理同一个数据不安全情况
from threading import Thread,Lock
import time
n = 100
def task(mutex):
global n
mutex.acquire() # 抢锁,只要有人抢到了锁其他人就必须等待该人释放锁
tmp = n
time.sleep(0.1)
n = tmp - 1
mutex.release() # 释放锁
t_list = []
mutex = Lock() # 生成一把锁
for i in range(100):
t = Thread(target=task,args=(mutex,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(n) # 0
TCP服务端通过线程实现并发
# 服务端
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
def talk(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0:break
print(data.decode('utf-8'))
conn.send(data.upper())
except ConnectionResetError as e:
print(e)
break
conn.close()
while True:
conn, addr = server.accept()
print(addr)
t = Thread(target=talk,args=(conn,))
t.start()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
client.send(b'hello')
data = client.recv(1024)
print(data.decode('utf-8'))
锁问题
GIL全局解释器锁
"""
GIL是Cpython解释器的特点(python解释器有多种,最常见的就是Cpython)
GIL本质是一把互斥锁(将并发变串行牺牲效率保证数据安全),用来阻止同一个进程下多个线程同时运行保证线程的安全(同一个进程下多个线程无法实现并行但可以实现并发),单进程下多个线程无法利用多核优势是所有解释型语言的通病
GIL的存在是因为CPython解释器的内存管理(垃圾回收机制)不是线程安全的(每个进程下都有个垃圾回收线程,当同一进程下多个线程同时运行时,会出现比如一个线程执行a=1,a还没指向1的内存就被垃圾回收线程回收了)
python多线程是否有用需要分情况讨论:
四个任务:计算密集型,一个任务需要10s
单核情况下:开线程更省资源
多核情况下:
开进程:10s
开线程:40s
四个任务:IO密集型
单核情况下:开线程更省资源
多核情况下:开线程更节省资源
"""
# 有了GIL为什么同一进程下多个线程还需要加锁?因为进入IO则GIL锁会自动释放
from threading import Thread
import time
n = 100
def task():
global n
tmp = n
time.sleep(1) # 进入IO,GIL锁会释放,则n为99,当没有IO则n为0
n = tmp -1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(n)
死锁
# 自己千万不要轻易的处理锁的问题
from threading import Thread,Lock,current_thread
import time
mutexA = Lock()
mutexB = Lock()
class MyThread(Thread):
def run(self): # 创建线程会自动触发run方法,run方法再调用func1 func2
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name
mutexB.acquire()
print('%s抢到了B锁'%self.name)
mutexB.release()
print('%s释放了B锁'%self.name)
mutexA.release()
print('%s释放了A锁'%self.name)
def func2(self):
mutexB.acquire()
print('%s抢到了B锁'%self.name)
time.sleep(1)
mutexA.acquire()
print('%s抢到了A锁' % self.name)
mutexA.release()
print('%s释放了A锁' % self.name)
mutexB.release()
print('%s释放了B锁' % self.name)
for i in range(10):
t = MyThread()
t.start()
"""
Thread-1抢到了A锁
Thread-1抢到了B锁
Thread-1释放了B锁
Thread-1释放了A锁
Thread-1抢到了B锁
Thread-2抢到了A锁 # 卡在这里了
"""
递归锁
"""
Rlock可以被当前抢到锁的线程连续的acquire和release
每acquire一次锁计数加1
每release一次锁计数减1
只要锁计数不为0其他线程都不能抢
"""
from threading import Thread,RLock
mutex = RLock() # A B现在是同一把锁
class MyThread(Thread):
def run(self):
mutex.acquire()
print('%s抢到了锁'%self.name) # self.name等价于current_thread().name
mutex.acquire()
print('%s抢到了锁'%self.name)
mutex.release()
print('%s释放了锁'%self.name)
mutex.release()
print('%s释放了锁'%self.name)
for i in range(3):
t = MyThread()
t.start()
"""
Thread-1抢到了锁
Thread-1抢到了锁
Thread-1释放了锁
Thread-1释放了锁
Thread-2抢到了锁
Thread-2抢到了锁
Thread-2释放了锁
Thread-2释放了锁
Thread-3抢到了锁
Thread-3抢到了锁
Thread-3释放了锁
Thread-3释放了锁
"""
信号量
"""
信号量可能在不同的领域中对应不同的知识点
互斥锁:一个厕所(一个坑位,同时只能一个线程抢到锁)
信号量:公共厕所(多个坑位,同时能有多个线程抢到锁)
"""
from threading import Thread,Semaphore
import random
sm = Semaphore(3) # 同时能有5个线程抢到锁
def task(name):
sm.acquire()
print('%s抢到锁'%name)
time.sleep(random.randint(1,3))
sm.release()
for i in range(5):
t = Thread(target=task,args=(i,))
t.start()
event事件
# 进程的join方法:主进程等待某个子进程运行结束才运行
# 线程的join方法:主线程等待某个子线程运行结束才运行
# event事件:子进程(线程)等待另一个子进程(线程)运行结束才运行
from threading import Thread,Event
import time
e = Event() # 生成event对象
def light():
print('红灯正亮着')
time.sleep(3)
print('绿灯亮了')
e.set() # 发信号
def car(name):
print('%s正在等红灯'%name)
e.wait() # 等待信号
print('%s加油门飙车了'%name)
t = Thread(target=light)
t.start()
for i in range(10):
t = Thread(target=car,args=('伞兵%s'%i,))
t.start()
"""
红灯正亮着
伞兵0正在等红灯
伞兵1正在等红灯
伞兵2正在等红灯
绿灯亮了
伞兵0加油门飙车了
伞兵1加油门飙车了
伞兵2加油门飙车了
"""
线程q
"""
同一个进程下的多个线程就是数据共享,为什么还要用队列?
因为队列是管道+锁,使用队列不需要手动处理锁,因为锁操作不好极容易产生死锁现象
"""
import queue
# Queue队列
q = queue.Queue()
q.put('hahha')
print(q.get()) # haha
# LifoQueue队列,堆栈先进后出
q = queue.LifoQueue()
q.put(1)
q.put(2)
print(q.get()) # 2
# PriorityQueue优先级队列,数字越小优先级越高
q = queue.PriorityQueue()
q.put((100,'haha'))
q.put((-10,'hehe'))
q.put((10,'xixi'))
print(q.get()) # hehe
进程池和线程池
"""
开进程和开线程都需要消耗资源,只不过线程消耗的资源比较少
什么是池?
在保证计算机硬件安全的情况下最大限度的利用计算机
池其实是降低了程序的运行效率,但保证了计算机硬件的安全(硬件的发展跟不上软件的速度)
进程池线程池: 放多个进程/线程的池子
池子中创建的进程/线程创建一次就不会再创建了,至始至终用的都是最初创建的,节省了反复开辟进程/线程的资源
提交任务的方式:
同步:提交任务后原地等待任务的返回结果,期间不做任何事
异步:提交任务后不等待任务的返回结果,直接执行下一行代码
异步的结果怎么拿:
result()
异步回调机制(add_done_callback):当异步提交的任务有了返回结果后会自动触发回调函数的执行
"""
# result()拿返回结果
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
pool = ProcessPoolExecutor(5) # 不指定进程池的进程数默认是当前计算机cpu的个数
# pool = ThreadPoolExecutor(5) # 不指定线程池的线程数默认是当前计算机cpu的个数*5
def task(n):
print(n)
time.sleep(2)
return n**2
if __name__ == '__main__':
t_list = []
for i in range(10):
res = pool.submit(task,i) # 朝进程/线程池中提交任务,异步提交
t_list.append(res)
# pool.shutdown() # 关闭池子,等待池子中所有任务执行完毕后才会往下运行
for p in t_list:
print('异步提交任务的返回结果:',p.result()) # 原地等待任务的返回结果
# 异步回调机制(add_done_callback)
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
# pool = ProcessPoolExecutor(5) # 不指定进程池的进程数默认是当前计算机cpu的个数
pool = ThreadPoolExecutor(5) # 不指定线程池的线程数默认是当前计算机cpu的个数*5
def task(n):
print(n)
time.sleep(2)
return n**2
def call_back(n):
print('异步提交任务的返回结果:',n.result())
if __name__ == '__main__':
for i in range(10):
res = pool.submit(task,i).add_done_callback(call_back) # 提交任务时绑定一个回调函数,一旦该任务有结果立刻执行回调函数
协程
"""
进程:资源单位
线程:执行单位
协程:单线程下实现并发(切换+保存状态)
程序员通过代码检测程序中的IO,一旦遇到IO通过代码切换,给操作系统的感觉是线程没有任何IO,从而保证程序在运行态和就绪态来回切换,提升代码的运行效率(yield能保存状态但不能识别IO)
"""
from gevent import monkey;monkey.patch_all() # gevent模块没办法识别time.sleep等io,需要配置这个参数
from gevent import spawn
import time
def heng():
print("哼")
time.sleep(2)
print('哼')
def ha():
print('哈')
time.sleep(3)
print('哈')
start = time.time()
g1 = spawn(heng) # spawn会检测heng和ha这两个任务,一旦heng有io就执行ha,ha有io就执行heng
g2 = spawn(ha)
g1.join() # 等待任务执行完成
g2.join()
print(time.time() - start)
TCP服务端通过协程实现并发
# 客户端起400个线程连接服务端
import socket
from threading import Thread,current_thread
def client():
client = socket.socket()
client.connect(('127.0.0.1',8080))
n = 0
while True:
data = '%s %s'%(current_thread().name,n)
client.send(data.encode('utf-8'))
res = client.recv(1024)
print(res.decode('utf-8'))
n += 1
for i in range(400):
t = Thread(target=client)
t.start()
# 服务端通过协程实现并发
from gevent import monkey;monkey.patch_all()
import socket
from gevent import spawn
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
def talk(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0:break
print(data.decode('utf-8'))
conn.send(data.upper())
except ConnectionResetError as e:
print(e)
break
conn.close()
def server1():
while True:
conn, addr = server.accept()
spawn(talk,conn)
if __name__ == '__main__':
g1 = spawn(server1)
g1.join()
IO模型
blocking IO 阻塞IO
nonblocking IO 非阻塞IO
IO multiplexing IO多路复用
signal driven IO 信号驱动IO
asynchronous IO 异步IO
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!