GIL锁

参考
https://www.cnblogs.com/clschao/articles/9705317.html
http://www.dabeaz.com/python/UnderstandingGIL.pdf
http://zhuoqiang.me/python-thread-gil-and-ctypes.html

GIL锁

在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。
GIL锁是Cpython特有的。

GIL介绍

GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
Cpython为什么有GIL锁

  • 历史遗留问题,Guido van Rossum创造python时(1989年圣诞节期间),多核CPU根本不存在,加上GIL锁后实现起来及经济又简单。
  • 那现在为什么不去掉,如果去掉GIL,要加入更多更细颗粒度的锁,实现难度颇大,而且改动的对象还是有28年历史的 CPython 代码树,更不论有这么多第三方的扩展也在依赖 GIL。

如何应用多核

  • 使用多进程,使用标准库mutilprocessing,让多进程的python简化到类似多线程的程度,多进程相对于多线程开销大。
  • 利用ctypes绕过GIL,ctypes可以使py直接调用任意的C动态库的导出函数。所要做的只是用ctypes写python代码即可。而且,ctypes会在调用C函数前释放GIL。
  • 关键部分用C/C++写成python的扩展。

GIL与Lock

GIL锁是解释器内同一时刻只有一个线程在执行;互斥锁同一时刻只有一个线程操作数据。
为什么有了GIL锁还会有数据不安全的问题:
当一个线程拿到一份拿数据还未做修改操作时,遇到了IO,这时会切到另外一个线程,另一个线程也拿到这份数据,这两个线程拿到的是同一份数据。
例子,AB线程都需要对num(10)做减一操作,A线程到数据num(10),遇到IO操作,切到B线程,B线程拿到num也是10,做减一操作,给num赋值9,结束,A线程IO结束,做减一操作,给num赋值9,结束。此时数据异常

GIL与多线程

Cpython无法利用多核优势,同一时刻只能有一个线程在执行。那是不是Cpython就没用了。PHP才是牛逼的语言。。。
不着急,看看CPU到底是干什么的。

  1. cpu到底是用来做计算的,还是用来做I/O的?
  2. 多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能
  3. 每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处

结论:对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地。

#分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程

#单核情况下,分析结果: 
  如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
  如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜

#多核情况下,分析结果:
  如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
  如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

 
#结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

多线程性能测试

IO密集型测试

from multiprocessing import Process
from threading import Thread
import os
import time


def work():
    time.sleep(2)
    print('===>')


if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机为2核4线程
    start = time.time()
    for i in range(400):
        # p = Process(target=work)  # 耗时49s多,47s在创建进程,大部分时间耗费在创建进程上。
        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
import time


def work():
    res = 0
    for i in range(100000000):
        res *= i


if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机为2核4线程
    start = time.time()
    for i in range(4):
        # p = Process(target=work)  # 耗时22s多
        p = Thread(target=work)  # 耗时43s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))

应用:
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析

posted @ 2018-12-05 21:23  写bug的日子  阅读(131)  评论(0编辑  收藏  举报