Python 进程线程协程 GIL 闭包 与高阶函数(五)
1 GIL线程全局锁
线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.对于io密集型任务,python的多线程起到作用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。
在分析线程全局锁之前我们先聊下python.
(1) python语言的症结
python是解释型语言 , 不像c++这样是编译型语言 : 程序输入到编译器,编译器再根据语言的语法进行解析 . 编译型语言之所以可以进行深层次的底层优化是因为可以直接看到代码的整体部分,这使得它可以对不同的语言指令之间的交互进行推理,从而给出更有效的优化手段。
Python是解释型语言,程序被输入到解释器运行,解释器只会按照python的规则以及在执行过程中怎样去动态的应用这些规则,由于解释器没法很好的对程序进行推导. 而Python大部分的优化也是解释器自身的优化, 换句话说,解释器优化后,Python不用修改源代码就能享受优化的好处. 因此关键问题来了,如果假定其他条件不变, python程序的执行速度跟解释器速度相关 . 不管你怎样优化自己的程序,你的程序的执行速度还是依赖于解释器执行你的程序的效率。这就很明显的解释了为什么我们需要对优化Python解释器做这么多的工作了。
(2) Python的多线程
要想利用多核系统,Python必须支持多线程运行. 目前来说,多线程执行还是利用多核系统最常用的方式 .,但是多个线程竞争一个共享资源数据将会是意见很糟糕的事, 因此解释器要留意的是避免在不同的线程操作内部共享的数据。同时它还要保证在管理用户线程时保证总是有最大化的计算资源。那么数据的安全机制怎么操作呢? 答案就是解释器全局锁 , 这是一个加在解释器上的全局锁。这种方式当然很安全,但是它隐含意思:对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在执行。
这就是为什么总会碰到 ”全新的多线程Python程序运行得比其只有一个线程的时候还要慢? ”
(3) 如何解决GIL
很多有经验的开发人员都会回答:”不要使用多线程,使用多进程!!”,但是这并没有解决多线程的核心问题, 国外有做过这方面的尝试,通过移除GIL,且用细粒度的锁来代替,但是带来的代价是单线程执行程序运行速度下降很大.在新的GIL中, 用一个固定的超时时间来指示当前的线程以放弃这个锁。在当前线程保持这个锁,且当第二个线程请求这个锁的时候,当前线程就会在5ms后被强制释放掉这个锁(这就是说,当前线程每5ms就要检查其是否需要释放这个锁)。当任务是可行的时候,这会使得线程间的切换更加可预测。 但是这也不是一个完美的方案, 因此GIL线程全局锁仍然是Python最大的挑战,希望有大牛能完美解决!!!!
2 线程 进程与协程
多线程
线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除 .
多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行。即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行的效果。
多线程相当于一个并发(concunrrency)系统。并发系统一般同时执行多个任务。如果多个任务可以共享资源,特别是同时写入某个变量的时候,就需要解决同步的问题.
在并发情况下,指令执行的先后顺序由内核决定。同一个线程内部,指令按照先后顺序执行,但不同线程之间的指令很难说清除哪一个会先执行。因此要考虑多线程同步的问题。同步(synchronization)是指在一定的时间内只允许某一个线程访问某个资源。
协程
与线程的抢占式调度不同,它是协作式调度。协程也是单线程,但是它能让原来要使用异步+回调方式写的非人类代码,可以用看似同步的方式写出来。
1 协程在python中可以由生成器(generator )来实现 即可以通过yield控制程序的运行
2 Stackless Python
3 greenlet模块
4 eventlet模块
多进程
multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。
1 进程池 (Process Pool)可以创建多个进程。
2 apply_async(func,args) 从进程池中取出一个进程执行func,args为func的参数。它将返回一个AsyncResult的对象,你可以对该对象调用get()方法以获得结果。
3 close() 进程池不再创建新的进程
4 join() wait进程池中的全部进程。必须对Pool先调用close()方法才能join。
3 闭包
在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 重点是函数运行后并不会被撤销,只是迁移到内函数上 ,总结一下,创建一个闭包必须满足以下几点:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
#闭包函数的实例 # outer是外部函数 a和b都是外函数的临时变量 def outer( a ): b = 10 # inner是内函数 def inner(): #在内函数中 用到了外函数的临时变量 print(a+b) # 外函数的返回值是内函数的引用 return inner if __name__ == '__main__': # 在这里我们调用外函数传入参数5 #此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo demo = outer(5) # demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数 demo() # 15 demo2 = outer(7) demo2()#17
外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数,因此外函数的结束并没有清空a,b的数值,而是绑定给了内函数。
4 高阶函数,函数式编程
(1)lambda函数
lambda函数也成为匿名函数是函数式编程的一种,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,同类的还有filter ,reduce等等
#将list每个元素进行平方 #map的语法是 : map(f,a) 也就是将函数 f 依次套用在 a 的每一个元素上面 map( lambda x: x*x, [y for y in range(10)] ) #类似于下面的写法 def sq(x): return x * x map(sq, [y for y in range(10)]) #多定义了一个函数,尤其在只是用一次的情况下
所以你会发现自己如果能将「遍历列表,给遇到的每个元素都做某种运算」的过程从一个循环里抽象出来成为一个高阶函数 map,然后用 lambda 表达式将这种运算作为参数传给 map 的话,思维水平就要高出一般的原写法。
(2)reduce函数
reduce()函数也是Python内置的一个高阶函数。
reduce()函数接收的参数和 map()类似,一个函数 f,一个list,但行为和 map()不同,reduce()传入的函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f,并返回最终结果值。
def f(x, y): return x + y reduce(f, [1, 3, 5, 7, 9]) #先计算头两个元素:f(1, 3),结果为4; #再把结果和第3个元素计算:f(4, 5),结果为9; #再把结果和第4个元素计算:f(9, 7),结果为16; #再把结果和第5个元素计算:f(16, 9),结果为25; #由于没有更多的元素了,计算结束,返回结果25
(3) filter函数
filter()函数是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
#list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数 def is_odd(x): return x % 2 == 1 filter(is_odd, [1, 4, 6, 7, 9, 12, 17]) #输出 [1, 7, 9, 17]
当然还可以实现很多功能,取决与函数定义的功能(删除 None 或者空字符串 等),filter仅仅是辅助函数起到过滤作用。
(4)函数式编程
def func(x): def funcx(y): return x+y return funcx func2 = func(2) func5 = func(5) print(func2(5)) # 输出 7 print(func5(5)) # 输出 10
inc()函数返回了另一个函数incx(),于是我们可以用inc()函数来构造各种版本的inc函数,比如:inc2()和inc5()。这个技术其实就是上面所说的Currying技术。从这个技术上,你可能体会到函数式编程的理念:把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/12195.html