上周五作业——多进程实现TCP服务端并发
# 服务端
import socket
from multiprocessing import Process
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 9999))
server.listen(5)
return server # 把封装好属性的socket对象传出去给其他函数使用
def get_talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
if __name__ == '__main__':
server = get_server() # 拿到socket对象
while True:
sock, addr = server.accept() # 建立通道,拿到客户端地址
# 开设多进程去聊天
p = Process(target=get_talk, args=(sock,))
p.start()
_______________________________
# 客户端没差别,老样子写普通版本的
一、互斥锁——Lock、mutex
将并发、并行变为串行,牺牲了效率但是提升了数据安全
from multiprocessing import Process,Lock
造锁
mutex = Lock()
用锁
mutex.acquire() # 抢锁,得放在操作数据层
mutex.release() # 放锁
二、线程理论
进程——资源单位
线程——执行单位
一个进程内部至少有一个线程!!!
进程是开辟内存空间,线程是在进程上执行不占内存空间,所以创建进程的资源占用是比创建进程大的多的
from threading import Thread
import time
def a(name):
time.sleep(0.01) # 因为线程执行的飞快,加一个阻塞试试
print(f'{name} 正在运行')
time.sleep(3)
print(f'{name} 执行完毕')
if __name__ == '__main__':
t = Thread(target=a, args=('jack',))
t.start()
print('主线程执行了')
>>>
主线程执行了
jack 正在运行
jack 执行完毕
三、线程的诸多方法
1.join方法
一样的,和进程
2.同一个进程下的多个线程数据共享
因为不是多个复制内存空间,所以是共用的数据
3.线程名字
类似进程ID一样的,current_thread
4.线程进程号
同一个进程下的多个线程都是一个进程号
5.统计同一个进程下的线程数
activeCount
注意如果没手动添加阻塞,用for循环生成多个线程,每循环生成一个执行完的线程就消失了
6.守护线程
和守护进程一样
7.互斥锁
也有Lock子模块,用法一样
四、GIL全局解释器锁——只在CPython有
1.介绍
1.在CPython解释器中存在全局解释器锁简称GIL
python解释器有很多类型
CPython JPython PyPython (常用的是CPython解释器)
2.GIL本质也是一把互斥锁 用来阻止同一个进程内多个线程同时执行(重要)
3.GIL的存在是因为CPython解释器中内存管理不是线程安全的(垃圾回收机制)
2.如何绕过GIL
from threading import Thread
import time
num = 1
def a():
global num
c = num
time.sleep(0.5) # 其实这里和抢票差不多,因为是多线程,让线程都睡一会,又重新弄了一个变量名,强行避开了GIL锁
num = c + 1
t_list = []
for i in range(1, 100):
t = Thread(target=a)
t.start()
t_list.append(t)
for t in t_list: # 确保所有的进程全部运行完毕
t.join()
print(num)
>>>
2
# 如果不在a中新弄变量名,GIL锁会影响,得到结果为100
GIL 只能确保同进程内多线程数据不会被垃圾回收机制弄乱,不能确保程序里面的数据安全
验证python多线程 是否有用
需要分情况
1.cpu情况
单个cpu
多个cpu
2.代码情况
io密集型(代码有io操作)
计算密集型(代码没有io)
3.单个cpu
- io密集型
1.多进程
总耗时(单进程的耗时+io+申请空间+拷贝代码)
2.多线程
总耗时(单进程的耗时加+io)
!!!多线程有优势
- 计算密集型
1.多进程
申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
2.多线程
耗时资源相对较少 通过多道技术(总耗时+切换)
!!!多线程有优势
4.多个cpu
- io密集型
1.多进程
总耗时(单个进程的耗时+io+申请空间+拷贝代码)
2.多线程
总耗时(单进程耗时+io)
!!!多线程有优势
- 计算密集型
1.多进程
总耗时(单个进程的耗时)
2.多线程
总耗时(多个进程的综合)
!!!多进程完胜!
五、信号量
在python并发编程中信号量相当于多把互斥锁(打个比方公共厕所)
在其他的知识中意思达到某个条件自动触发其他操作
from threading import Thread, Lock, Semaphore
import time
import random
sp = Semaphore(5) # 一次性产生五把锁
class MyThread(Thread):
def run(self):
sp.acquire()
print(self.name)
time.sleep(random.randint(1, 3))
sp.release()
for i in range(20):
t = MyThread()
t.start()
# 相当于让20个人去挤厕所,厕所只有五个位置,哪个位置有人出来就可以新的人进去上
六、event事件——可以暂停线程或者进程
from threading import Thread, Event
import time
event = Event() # 造一个event对象
def light():
print('红路灯红灯亮着,等待绿灯>>>')
time.sleep(1)
print('3')
time.sleep(1)
print('2')
time.sleep(1)
print('1')
time.sleep(1)
print('绿灯亮了!游戏开始!')
event.set() # 让被等待的对象立马执行
def car(name):
print(f'{name} 正在起跑线等红绿灯')
event.wait() # 让根据这个函数产生的进程等着
print(f'游戏开始! {name}冲刺! ')
t = Thread(target=light)
t.start() # 这个函数产生的进程睡期间下面的代码执行,立马生成进程
for i in range(1, 10):
t = Thread(target=car, args=(f'棉花糖SR{i}',))
t.start()
>>>
红路灯红灯亮着,等待绿灯>>>
棉花糖SR1 正在起跑线等红绿灯
棉花糖SR2 正在起跑线等红绿灯
棉花糖SR3 正在起跑线等红绿灯
棉花糖SR4 正在起跑线等红绿灯
棉花糖SR5 正在起跑线等红绿灯
棉花糖SR6 正在起跑线等红绿灯
棉花糖SR7 正在起跑线等红绿灯
棉花糖SR8 正在起跑线等红绿灯
棉花糖SR9 正在起跑线等红绿灯
3
2
1
绿灯亮了!游戏开始!
游戏开始! 棉花糖SR4冲刺!
游戏开始! 棉花糖SR5冲刺!
游戏开始! 棉花糖SR2冲刺!
游戏开始! 棉花糖SR1冲刺!
游戏开始! 棉花糖SR8冲刺!
游戏开始! 棉花糖SR6冲刺!
游戏开始! 棉花糖SR3冲刺!
游戏开始! 棉花糖SR7冲刺!
游戏开始! 棉花糖SR9冲刺!
七、进程池、线程池
能提前设置好最大进程、线程数量,可以有预防性的防止电脑因为进程或线程太多卡死
当然,因为做出了限制,所以程序的执行效率是降低了
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
# 1产生固定数量的线程值
p = ProcessPoolExecutor(12)
# pool = ThreadPoolExecutor(10)
def test(name):
print(f'测试正在执行 测试员{name}>>>')
time.sleep(2)
print(f'测试执行完毕 测试员{name}>>>')
return '工作完成'
def func(a):
print('我来负责收结果')
print(a.result()) # 把返回值打印出来,得用result()
if __name__ == '__main__': # 进程别忘了main
for i in range(1, 24):
# 直接往池子里塞任务,池子按照设置好的数量分批创建进程
p.submit(test, 'jack').add_done_callback(func) # 通过调专门接收结果的函数来获取进程执行完的结果
八、协程——gevent第三方模块
因为CPU才是干活的核心,而程序执行过程中如果碰到io操作,CPU会暂时离开,协程技术能欺骗CPU伪装代码中没有io操作,能让CPU一直陪同跟随,达到最大效率的目的。
这个模块不需要去了解底层原理,这是别的大佬封装好的方法,会用就行
from gevent import monkey;
monkey.patch_all() # 固定语法,用于检测所有的IO操作(猴子补丁)
from gevent import spawn
import time
def a():
print('a 正在运行')
time.sleep(2)
print('a 执行结束')
def b():
print('b 正在运行')
time.sleep(3)
print('b 执行结束')
if __name__ == '__main__':
start_time = time.time()
a()
b()
print(time.time() - start_time)# >>> 5.056353569030762
————————————————————————————————————————————————————————
if __name__ == '__main__':
start_time = time.time()
s1 = spawn(a)
s2 = spawn(b)
s1.join()
s2.join()
print(time.time() - start_time) # >>>3.0264976024627686
九、单线程实现高并发
# 服务端
from gevent import monkey;
monkey.patch_all()
from gevent import spawn
import socket
def a(sock):
while True:
date = sock.recv(1024)
print(date.decode('utf8'))
sock.send(date.upper())
def s():
sever = socket.socket()
sever.bind(('127.0.0.1', 8088))
sever.listen(5)
while True:
sock, addr = sever.accept()
spawn(a, sock)
s1 = spawn(s)
s1.join()
————————————————————————————————————————————————————
# 客户端
import socket
c1 = socket.socket()
c1.connect(('127.0.0.1', 8088))
while True:
c1.send(b'hello world')
date = c1.recv(1024)
print(date.decode('utf8'))
# 记得去开pycharm中单一个Py文件的重复执行
↑↑↑↑↑↑↑↑