GIL全局解释器锁
什么是GIL?
GIL本质就是一个把互斥锁,是将多个并发的线程对共享数据的修改变成“串行”(并非真的串行,详情: http://www.cnblogs.com/linhaifeng/articles/7449853.html,第三节GIL和Lock)
为何有?
Cpython解释器的垃圾回收机制不是线程安全的如何用?
有GIL的存在,导致同一个进程内的多个线程同一时刻只能有一个运行,即同一进程的多个线程 无法实现运行===》无法利用多核优势
但是可以实现并发的效果
什么是多核优势?
多核即多个CPU,多个CPU带来的优势是计算性能的提升,所以I/O密集型:
多线程
计算密集型:
多进程GIL vs 自定义互斥锁
GIL相当于执行权限,意思是在一个进程内的多个线程想要执行,必须先抢GIL,这把锁的特点是,当一个线程被剥夺走cpu的执行权限的同时会被解释器强行释放GIL2、进程池与线程池?
什么是?
池:池用来限制进程/线程个数的一种机制
为何用?
当并发的任务数远大于计算机的承受能力,应该用池的概念
将并发的进程数/线程数控制在计算机能承受的数目如何用?
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def task(n): return n**2 def func(future): print(future.result()) if __name__ == '__main__': p=ProcessPoolExecutor(4) p.submit(task,10).add_done_callback(func) # add_done_callback(fn):回调函数,func会在p.submit(task,10))有返回值时立刻触发,并且将p.submit(task,10)当作参数传给func p.shutdown(wait=True) # pool.close() # pool.join() print('主')
提交任务的两种方式:
1、同步:提交完任务后就在原地等待,直到任务运行完毕并且拿到返回值后,才运行下一行代码
2、异步:提交完任务(绑定一个回调函数)后不原地等待,直接运行下一行代码,等到任务运行有返回值自动触发回调的函数的运行程序的运行状态(阻塞,非阻塞)
1、阻塞
I/O阻塞
2、非阻塞
运行
就绪
协程
1、协程
单线程实现并发
在应用程序里控制多个任务的切换+保存状态
优点:
应用程序级别速度要远远高于操作系统的切换
缺点:
多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地,该线程内的其他的任务都不能执行了
一旦引入协程,就需要检测单线程下所有的I/O行为,实现遇到I/O就切换,少一个都不行,因为一旦一个任务阻塞了,整个线程就阻塞了,其他的任务即使是可以计算,但是也无法运行了
2、协程的目的:
想要在单线程下实现并发
并发指的是多个任务看起来是同时运行的
并发=切换+保存状态
# 串行执行
import time
def func1():
for i in range(10000000)
i+1
def func2():
for i in range(10000000):
i+1
start=time.time()
func1()
func2()
stop=time.time()
print(stop - start)
'''
结果:
1.4379246234893799
'''
# 基于yield并发执行
import time
def func1():
while True:
print('func1')
yield
def func2():
g=func1()
for i in range(10000000):
print('func2')
i+1
time.sleep(3)
next(g)
start=time.time()
func2()
stop=time.time()
print(stop - start)
'''
结果:
func2
--等待3秒--
func1
func2
--等待3秒--
func1
func2
--等待3秒--
func1
func2
--等待3秒--
func1
func2
--等待3秒--
func1
......
'''
使用gevent模块:Gevent是第三方库,需要提前安装在python中才能使用,此模块可以通过gevent实现并发同步或者异步编程。
gevent直接使用不能识别time.sleep,需要通过打补丁:monkey.path_all(),并且放在被打补丁者(import.time)之前生效,简写成下方首先代码即可。
安装方法:pip3 install gevent
from gevent import monkey,spawn;monkey.patch_all()
# 注意上下代码顺序不可颠倒过来
import time
def eat(name):
print('%s eat 1' %name)
time.sleep(3)
print('%s eat 2' %name)
def play(name):
print('%s play 1' %name)
time.sleep(1)
print('%s play 2' %name)
start=time.time()
g1=spawn(eat,'egon')
g2=spawn(play,'zmy')
g1.join()
g2.join() #此处两个join可以有简写方式:gevent.joinall([g1,g2])
print(time.time() - start)
print(g1)
print(g2)
'''
结果:
egon eat 1
zmy play 1
zmy play 2
egon eat 2
3.0080697536468506
<Greenlet "Greenlet-0" at 0x3329ad0: _run>
<Greenlet "Greenlet-1" at 0x3329b58: _run>
'''
from gevent import monkey,spawn;monkey.patch_all()
from threading import current_thread
import time
def eat():
print('%s eat 1' %current_thread().name)
time.sleep(3)
print('%s eat 2' %current_thread().name)
def play():
print('%s play 1' %current_thread().name)
time.sleep(1)
print('%s play 2' %current_thread().name)
g1=spawn(eat,)
g2=spawn(play,)
print(current_thread().name)
g1.join()
g2.join()
'''
结果:
MainThread
DummyThread-1 eat 1 #DummyThread为虚拟线程
DummyThread-2 play 1
DummyThread-2 play 2
DummyThread-1 eat 2
'''
# 并发的套接字通信
# 服务端:
from gevent import spawn,monkey;monkey.patch_all()
from socket import *
from threading import Thread
def talk(conn):
while True:
try:
data=conn.recv(1024)
if len(data) == 0:break
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
def server(ip,port,backlog=5): #传入ip,port,backlog=5(默认为5)
server = socket(AF_INET, SOCK_STREAM)
server.bind((ip, port))
server.listen(backlog)
print('starting...')
while True:
conn, addr = server.accept()
spawn(talk, conn,)
if __name__ == '__main__':
g=spawn(server,'127.0.0.1',8080)
g.join()
# 客户端:
from threading import Thread,current_thread
from socket import *
import os
def task():
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
msg='%s say hello' %current_thread().name
client.send(msg.encode('utf-8'))
data=client.recv(1024)
print(data.decode('utf-8'))
if __name__ == '__main__':
for i in range(500):
t=Thread(target=task)
t.start()
I/O模型
网络I/O:
recvfrom:
wait data:等待客户端产生数据————》客户端OS————》网络————》服务端操作系统缓存
copy data:由本地操作系统缓存中的数据拷贝到应用程序的内存中
非阻塞I/O模型
# 服务端:
from socket import *
import time
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
server.setblocking(False)
conn_l=[]
while True:
try:
print('总连接数[%s]' % len(conn_l))
conn,addr=server.accept()
conn_l.append(conn)
except BlockingIOError:
del_l=[]
for conn in conn_l:
try:
data=conn.recv(1024)
if len(data) == 0:
del_l.append(conn)
continue
conn.send(data.upper())
except BlockingIOError:
pass
except ConnectionResetError:
del_l.append(conn)
for conn in del_l:
conn_l.remove(conn)
# 客户端:
from socket import *
import os
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
msg='%s say hello' %os.getpid()
client.send(msg.encode('utf-8'))
data=client.recv(1024)
print(data.decode('utf-8'))