python全栈开发从入门到放弃之socket并发编程多线程GIL
一 介绍
'''
定义:
In CPython, the global interpreter lock, or 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.)
'''
结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
首先需要明确的一点是GIL
并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL
归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf
二 GIL介绍
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。
要想了解GIL,首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不同的python进程
'''
#验证python test.py只会产生一个进程
#test.py内容
import os,time
print(os.getpid())
time.sleep(1000)
'''
python3 test.py
#在windows下
tasklist |findstr python
#在linux下
ps aux |grep python
验证python test.py只会产生一个进程
在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内,毫无疑问
#1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。 #2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。
综上:
如果多个线程的target=work,那么执行流程是
多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行
解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
三 GIL与Lock
GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理,如下图
四 GIL与多线程
有了GIL的存在,同一时刻同一进程中只有一个线程被执行
听到这里,有的同学立马质问:进程可以利用多核,但是开销大,而python的多线程开销小,但却无法利用多核优势,也就是说python没用了,php才是最牛逼的语言?
要解决这个问题,我们需要在几个点上达成一致:
#1. cpu到底是用来做计算的,还是用来做I/O的? #2. 多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能 #3. 每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处
一个工人相当于cpu,此时计算相当于工人在干活,I/O阻塞相当于为工人干活提供所需原材料的过程,工人干活的过程中如果没有原材料了,则工人干活的过程需要停止,直到等待原材料的到来。
如果你的工厂干的大多数任务都要有准备原材料的过程(I/O密集型),那么你有再多的工人,意义也不大,还不如一个人,在等材料的过程中让工人去干别的活,
反过来讲,如果你的工厂原材料都齐全,那当然是工人越多,效率越高
结论:
对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地
#分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程 #单核情况下,分析结果:
如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜 #多核情况下,分析结果:
如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜 #结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
五 多线程性能测试
I\O密集型:多线程效率高
多线程
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2) #模拟io延迟
print('===>') if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(400): #开启400个线程
# p=Process(target=work) #耗时17s多,大部分时间耗费在创建进程上
p=Thread(target=work) #耗时2s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start)) 多进程
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2) #模拟io延迟
print('===>') if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(400):
p=Process(target=work) #耗时17s多,大部分时间耗费在创建进程上
# p=Thread(target=work) #耗时2s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
计算密集型:多进程效率高
多线程
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(100000000):
res*=i if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(4):
# p=Process(target=work) #耗时5s多
p=Thread(target=work) #耗时18s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
'''
输出结果:
4
run time is 23.561766386032104 ''' 多进程:
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(100000000):
res*=i if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(4):
p=Process(target=work) #耗时5s多
# p=Thread(target=work) #耗时18s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
'''
结果: 4
run time is 11.244367361068726
'''
应用:
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析
十五 Python标准模块--concurrent.futures
concurrent.futures - 启动并行任务
该
concurrent.futures
模块提供了一个用于异步执行callable的高级接口。
可以使用线程,使用 ThreadPoolExecutor或单独的进程 来执行异步执行 ProcessPoolExecutor。两者都实现了由抽象 Executor类定义的同一个接口。
执行对象
类concurrent.futures.
Executor
一个抽象类,提供异步执行调用的方法。它不应该直接使用,而是通过其具体的子类
submit
(fn,* args,** kwargs )-
调度可执行的fn,并执行, 并返回一个表示可执行程序执行的对象。
fn(*args **kwargs)
Future
用 的ThreadPoolExecutor (max_workers = 1 ) 作为 执行者:
未来 = 执行。提交(POW , 323 , 1235 )
打印(未来。结果())
map
(func,* iterables,timeout = None,chunksize = 1 )-
相当于除外FUNC异步执行,并多次打电话FUNC可以同时做。返回的迭代器引发一个if 被调用,并且在从原始调用到超时秒之后结果不可用。 超时可以是一个int或一个float。如果没有指定超时,或者 等待时间没有限制。如果调用引发异常,那么当从迭代器检索到该值时,将引发该异常。当使用时,这种方法会排除迭代
map(func,*iterables)
concurrent.futures.TimeoutError
__next__()
Executor.map()
None
ProcessPoolExecutor
分成若干块作为单独任务提交给游泳池的块。这些块的(近似)大小可以通过将chunksize设置为正整数来指定。对于很长的iterables,采用大值CHUNKSIZE可以显著改善性能相比的1.默认大小ThreadPoolExecutor
, CHUNKSIZE没有效果。在版本3.5中更改:添加了chunksize参数。
shutdown
(wait = True )-
向执行人发出信号,当目前待定的期货完成执行时,它应该释放它正在使用的任何资源。关机后打电话
Executor.submit()
和Executor.map()
提醒RuntimeError
。如果等待,
True
那么该方法将不会返回,直到所有待处理的期货完成执行,并且与执行器相关联的资源已被释放。如果等待,False
则此方法将立即返回,并且在执行所有挂起的期货时,与执行器相关联的资源将被释放。无论等待的价值如何,整个Python程序将不会退出,直到所有待处理的期货完成执行。如果您使用该
with
语句,则可以明确地调用此方法 ,这将关闭Executor
(等待Executor.shutdown()
,等待设置为 等待True
):
进程池:
#进程池
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time,random
def task(n):
print('%s is running' %os.getpid())
time.sleep(2)
return n**2 if __name__ == '__main__':
p=ProcessPoolExecutor(max_workers=4) #创建进程池,max_workers=多少是指同时开多少个进程,不填的话默认为CPU的核数
l=[]
start=time.time() #开始时间
for i in range(10):
obj=p.submit(task,i) #submit 提交()里面是指定函数名和要传的参数,然后拿到一个对象
l.append(obj) #把对象存到列表中
p.shutdown() #等待进程池里的进程结束
print('='*30)
print([obj for obj in l]) #可以看到列表中的存放的对象
print([obj.result() for obj in l]) #把列表中的对象进行轮询遍历,python设计真的很好用obj.result直接得到列表中先前对象的结果
print(time.time()-start) #计算时间 '''
输出结果:
9284 is running
12328 is running
13032 is running
7604 is running #可以看到,就这四个进程一直在工作
9284 is running
12328 is running
13032 is running
7604 is running
9284 is running
12328 is running
============================== #我是华丽的分割线
[<Future at 0x1d80d31e320 state=finished returned int>, <Future at 0x1d80d359ac8 state=finished returned int>, <Future at 0x1d80d359fd0 state=finished returned int>, <Future at 0x1d80d36b0b8 state=finished returned int>, <Future at 0x1d80d36b160 state=finished returned int>, <Future at 0x1d80d36b208 state=finished returned int>, <Future at 0x1d80d36b2e8 state=finished returned int>, <Future at 0x1d80d36b3c8 state=finished returned int>, <Future at 0x1d80d36b4a8 state=finished returned int>, <Future at #拿到对象存在列表中
0x1d80d36b588 state=finished returned int>]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] #直接通过result拿到对象的结果
6.199268341064453 #耗时 '''
进程池
线程池
#线程池
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor #调用线程池模块
import threading #查看线程的名字
import os,time,random
def task(n):
print('%s:%s is running' %(threading.currentThread().getName(),os.getpid()))
time.sleep(2)
return n**2 if __name__ == '__main__':
p=ThreadPoolExecutor(max_workers=5) #创建线程池,max_workers=指定的线程池里的线程数量,不写为默认的CPU核数*5
l=[] #创建一个列表
start=time.time() #计算时间
for i in range(10):
obj=p.submit(task,i) #提交提交()里面是指定函数名和要传的参数,然后拿到一个对象
l.append(obj) #把对象放到列表中
p.shutdown() #等待线程池里的线程结束
print('='*30)
print([obj.result() for obj in l]) #通过result提取列表中的对象,直接拿到结果
print(time.time()-start) '''
输出结果:
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_0:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_1:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_2:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_3:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_4:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_5:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_6:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_7:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_8:4616 is running
<concurrent.futures.thread.ThreadPoolExecutor object at 0x0000021FA0C7CA20>_9:4616 is running
==============================
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
2.003546714782715 '''
线程池
升级用法
#p.submit(task,i).result()即同步执行
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time,random
def task(n):
print('%s is running' %os.getpid())
time.sleep(2)
return n**2 if __name__ == '__main__':
p=ProcessPoolExecutor()
start=time.time()
for i in range(10):
res=p.submit(task,i).result()
print(res)
print('='*30)
print(time.time()-start)
p.submit(task,i).result()即同步执行
map高级用法
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time,random
def task(n):
print('%s is running' %os.getpid())
time.sleep(2)
return n**2 if __name__ == '__main__':
p=ProcessPoolExecutor()
obj=p.map(task,range(10)) #map函数很强大,第一个参数接收一个函数名,第二参数接收一个可迭代的对象,然后以列表的方式返回回来
p.shutdown()
print('='*30)
print(list(obj))
'''
输出结果:
4624 is running
13748 is running
10504 is running
8620 is running
4624 is running
13748 is running
10504 is running
8620 is running
4624 is running
13748 is running
==============================
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] '''
map函数通过线程池和进程池的升级用法
可以参考官方文档
https://docs.python.org/dev/library/concurrent.futures.html
python全栈开发从入门到放弃之socket并发编程多线程GIL的更多相关文章
- python全栈开发从入门到放弃之socket并发编程多线程
一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍 二 开启线程的两种方式 from threadi ...
- python全栈开发从入门到放弃之socket并发编程多进程
1.1 multiprocessing模块介绍 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程 ...
- python全栈开发从入门到放弃之socket并发编程之协程
一.为什么会有协程 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两种情 ...
- python全栈开发从入门到放弃之socket网络编程基础
网络编程基础 一 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务 ...
- python全栈开发从入门到放弃之socket并发编程之IO模型
一 IO模型介绍 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问 ...
- python全栈开发从入门到放弃之迭代器生成器
1.python中的for循环 l = [1,2,3,4,5,6] for i in l: #根据索引取值 print(i) 输出结果: 1 2 3 4 5 6 2.iterable 可迭代的 可迭 ...
- python全栈开发从入门到放弃之递归函数的调用
1.递归效率低,需要在进入下一次递归时保留当前的状态,见51cto博客 解决方法是尾递归,即在函数的最后一步(而非最后一行)调用自动但是python又没有尾递归,且对递归层级做了限制 必须有一个明确的 ...
- python全栈开发从入门到放弃之初识面向对象
面向过程 VS 面向对象 面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西. 优点是:极大的降低了写程序的复 ...
- python全栈开发从入门到放弃之面向对象的三大特性
组合 class Course: def __init__(self,name,period,price): self.name = name self.period = period self.pr ...
随机推荐
- 假设A.jsp内设定一个<jsp:useBean>元素:
假设A.jsp内设定一个<jsp:useBean>元素: <jsp:useBean id=”bean1” class=”myBean” /> 下列哪一个为真?(选择1项) A. ...
- PyQT实现扩展窗口,更多/隐藏
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVcAAAFeCAIAAAC+XMuHAAAgAElEQVR4nOy9Z5Ac153g2fx0F2fiYi ...
- 谈谈Jquery ajax中success和complete有哪些不同点
记录下,以备将来有需时用 http://www.jb51.net/article/75206.htm
- php求数学对数
php的对数函数并不是很强大 有自然对数 有10的对数的函数,不过没有自定义底的对数函数,所以自己写了一个 <?php function xsqrt($x, $value) { $count = ...
- js获取格式化后的当前时间
代码如下: function getFormatDate() { var day=new Date(); var Year=0; var Month=0; var Day=0; var Hour = ...
- codevs 5964 [SDOI2017]序列计数
[题解] 官方题解就两句话. 写了三个版本的不同分值代码.看代码吧. 前导1 //f[i][j][1/0]表示长为i,sum mod p=j,是否已经选了质数的方案数 #include<cst ...
- ORA-01102的解决办法
启动数据库时报错了! SQL> startup mount ORACLE instance started. Total System Global Area 608174080 bytes ...
- window 发布已编译好的ASP文件到IIS
1.进入window 7的控制面板,点击程序,选择程序和功能中的 打开或关闭Windows功能.安装IIS
- quartz 调度启动失败,with (updlock,rowlock)
原因是driverDelegateClass配置错误. org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTXorg.q ...
- Strut2流程分析-----从请求到Action方法()
手写请求会通过strutsPrepareAndExcuteFliter的doFilter()方法 然后会调用StrutsActionProxy类的excute()方法,生成一个代理类(ActionPr ...