网络并发编程05--终
1. GIL全局解释器锁
面试问到频率比较高
In CPython, the global interpreter lock, GIL, is a mutex that prevents multiple native
threads from executing Python bytecodes at once.
This lock is necessary mainly because CPython’s memory management is not thread-safe.
(However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
"""
1. python解释器其实有很多的版本(默认肯定使用的是CPython)
CPython, Jpython, pypython
在CPython中 GIL 全局解释器锁,其实也是一把互斥锁
主要用于阻止同一个进程下的多个线程同时被运行
(python的多线程无法使用多核优势)
GIL肯定存在于CPython解释器中,主要原因就在于CPython解释器的内存管理,部署线程安全的。
2. 内存管理 >>> 垃圾回收机制
引用计数
标记清除
分代回收
"""
1. GIL是CPython解释器的特点。
2. python同一个进程内的多个线程无法利用cpu的多核优势(无法并行,但是可以并发)
3. 同一个进程内的多个线程要想运行必须先抢GIL锁。
4. 所有的解释型语言,几乎都无法实现同一个进程下的多个线程同时被运行
2. 验证GIL 的存在及功能
# 验证GIL.py
from threading import Thread
import time
m = 100
def test():
global m
tmp = m
tmp -= 1
m = tmp
for i in range(100):
t = Thread(target=test)
t.start()
time.sleep(3)
print(m) # 0
#*************************************#
m = 100
def test():
global m
tmp = m
time.sleep(1)
tmp -= 1
m = tmp
for i in range(100):
t = Thread(target=test)
t.start()
time.sleep(3)
print(m) # 99
"""
同一个进程下的多个线程虽然会有 GIL 的存在,不会出现并行的效果,
但是如果线程内有IO操作还是会造成数据的错乱,这个时候需要我们额外的添加互斥锁。
"""
3. 死锁现象
from threading import Thread, Lock
a = Lock()
b = Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
a.acquire()
print('%s 抢到了a锁' % self.name)
# current_thread().name
b.acquire()
print('%s 抢到了b锁' % self.name)
time.sleep(1)
b.release()
print('%s 释放了b锁' % self.name)
a.release()
print('%s 释放了b锁' % self.name)
def func2(self):
b.acquire()
print('%s 抢到了b锁' % self.name)
a.acquire()
print('%s 抢到了a锁' % self.name)
a.acquire()
print('%s 释放了b锁' % self.name)
b.release()
print('%s 释放了b锁' % self.name)
for i in range(10):
obj = MyThread()
obj.start()
4. 验证python多线程是否有用
# 是否有用需要视情况而定(程序的类型)
# IO密集型
eg:四个任务,每个任务耗时 10s
# 先以单核为例
开设多进程没有太大的优势 10s+
因为遇到IO就需要切换,并且开设进程还需要申请内存空间和拷贝代码
开设多线程有优势 10s+
不需要消耗额外的资源
# 计算密集型
eg:四个任务,每个任务耗时 10s
开设多进程可以利用多核优势 10s+
开设多线程无法利用多核优势 40s+
"""
可以多进程结合多线程
"""
################ 代码 ##############
from threading import Thread
from multiprocessing import Process
import threading
import os, time
def work():
time.sleep(2)
if __name__ == '__main__':
l = []
print(os.cpu_count())
start = time.time()
for i in range(400):
p = Process(target=work)
# p = Thread(target=work)
l.append(p)
for p in l:
p.join()
stop = time.time()
print('run time is %s' %(stop-start))
#***************************#
"计算密集型"
from multiprocessing import Process
from threading import Thread
from os, time
def work():
res = 0
for i in range(20000000):
res *= i
if __name__ == '__main__':
l = []
print(os.cpu_count())
start = time.time()
for i in range(6):
p = Process(target=work)
# p = Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print('run time is %s' %(stop-start))
5. 进程池和线程池
# 能否无限制的开设进程或者线程??????
肯定不能
如果从技术层面上说,肯定没问题,且高效
但是从硬件层面上来说是无法实现的
(硬件的发展永远赶不上软件的发展速度)
# 池
在保证计算机硬件不崩溃的前提下,开设多进程和多线程
降低了程序的运行效率,但是保证了计算机的硬件安全
# 进程池与线程池
"进程池:"提前开设了固定个数的进程,之后反复调用这些进程完成工作。(后续不再开设新的)
"线程池:"提前开设了固定个数的线程,之后反复调用这些线程完成工作。(后续不再开设新的)
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
# 创建进程池,线程池
pool = ProcessPoolExecutor() # 池子里面的进程数默认与当前计算机cpu核数保持一致
# 线程池
tpool = ThreadPoolExecutor() # 池子里面的线程数默认与当前计算机cpu核数的5倍
def task(n):
print('开始')
res = 1
for i in range(n):
res = res += 1
time.sleep(1)
# print(res)
return res
# 向线程池中提交任务
obj_list = []
for i in range(100):
ret = pool.submit(task, i) # 异步提交任务
obj_list.append(ret)
pool.shutdown() # 等待池子中的所有任务运行完毕之后再关闭池子并往后运行
for i in obj_list:
print(ret.result()) # 同步提交:提交完任务原地等待任务的结果
# print('主')
6. 进程池与线程池基本使用
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
import os
# 线程池
pool = ThreadPoolExecutor(5) # 可以自定义线程数,也可以采用默认策略
# 定义一个任务
def task(n):
print(n)
time.sleep(2)
return '>>>:%s' % n**2
# 向线程池中提交任务
obj_list = []
for i in range(20):
res = pool.submit(task, i) # 异步提交
obj_list.append(res)
# print(res.result()) # 获取任务的执行结果,同步提交
for i in obj_list:
print(i.result())
# 等待线程池中所有的任务执行完毕之后,再获取各自任务的结果,
pool.shutdown()
for i in obj_list:
print(i.result())
# jin程池
pool = ProcessPoolExecutor(5) # 可以自定义进程数,也可以采用默认策略
# 定义一个任务
def task(n):
print(n,os.getpid())
time.sleep(2)
return '>>>:%s' % n**2
# 定义一个回调函数:异步提交完之后有结果自动调用该函数
def call_back(a):
print('异步回调函数:%s' % a.result)
# 向进程池中提交任务
if __name__ == '__main__':
for i in range(20):
res = pool.submit(task, i).add_done_callback(call_back)
"""
同步:提交完任务之后,原地等待任务的返回结果,期间不做任何事情,
异步:提交完任务之后,不原地等待任务的返回结果,结果由异步回调机制自动反馈
"""
# pool.shutdown()
# for i in obj_list:
# print(i.result())
7. 协程理论与实操
进程
资源单位
线程
工作单位
协程
是程序员单方面意淫出来的名词>>>:单线程下实现并发
# cpu被剥夺的条件
1.程序长时间占用
2.程序进程IO操作
# 并发
切换 + 保存状态
以往学习是:多个任务(进程,线程)来回切换
# 欺骗cpu的行为
单线程下我们如果能够自己监测IO操作,并且自己实现代码层面的切换,
那么对于cpu而言我们这个程序就没有IO操作
cpu会尽可能的被占用
"""代码层面"""
第三方gevent模块:能够自主监测IO行为并切换
代码
from gevent import monkey;monkey.patch_all() # 固定代码格式,加上之后才能监测所有的io行为
from gevent import spawn
import time
def play(name):
print('%s play 1' % name)
time.sleep(5)
print('%s play 2' % name)
def eat(name):
print('%s eat 1' % name)
time.sleep(3)
print('%s eat 2' % name)
start = time.time()
play('jack')
eat('jack')
g1 = spawn(play, 'jason') # 异步提交,
g2 = spawn(eat, 'jason') # 异步提交
g1.join()
g2.join() # 等待被监测的任务执行完毕
# joinall([g1,g2])
print('主', time.time() - start)
# 单线程下实现并发,提示效率
协程实现TCP服务端并发的效果
# 一个服务端同时服务多个客户端
import socket
from gevent import monkey;monkey.patch_all()
from gevent import spawn
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
def talk(conn):
while True:
try:
data = conn.recv(1024)
print(data)
conn.send(data+b'hello baby!')
except ConnectionResetError as e:
print(e)
conn.close()
break
def servers():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
while True:
conn, addr = server.accept()
talk(conn)
if __name__ == '__main__':
g1 = spawn(servers)
g1.join()
# 客户端开设几百个线程发消息即可
"""
最nb的情况:多进程下开多线程,多线程下开协程
自己以后可能动手写的不多,一般都是使用别人封装好的模块或框架
"""
客户端
from threading import Thread, current_thread
from socket import *
def client():
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
n = 0
while True:
msg = '%s say hello %s' % (current_thread().name, n)
n += 1
client.send(msg.encode('utf8'))
data = client.recv(1024)
print(data.encode('utf8'))
if __name__ == '__main__':
for i in range(500):
t = Thread(target=client)
t.start()
3. IO模型
"""理论为主,代码实现,大部分为伪代码(没有实际含义,仅为验证参考)"""
# IO模型研究的主要是网络IO
# 同步 synchronous
大部分缩写 sync
# 异步 asynchronous
async
# 阻塞 blocking
# 非阻塞 non-blocking
四种IO模型
# 阻塞IO
最为常见的一种IO 模型,有两个等待的阶段
waiting for data
# 非阻塞IO
系统调用阶段变为了非阻塞(轮询),有一个等待的阶段
copy data
轮询的阶段是比较消耗资源的
# 多路复用IO
利用select或者epoll来监管多个程序,一旦某个程序需要的数据存在于内存中了,那么立刻通知该程序去取即可
# 异步IO
只需要发起一次系统调用,之后无需频繁发送,有结果并准备好之后会通过异步回调机制反馈给调用者