• GIL全局解释器锁与协程


    进程和线程的比较

    1. 进程的开销比线程的开销大很多


    2. 进程之间的数据是隔离的,但是,线程之间的数据不隔离


    3. 多个进程之间的线程数据不共享----->还是让进程通信(IPC)------->进程下的线程也通信了---->队列

    GIL全局解释器

    Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。

    对Python解释器的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线
    程在运行

    背景信息:


    1. Python代码运行在解释器上嘛,有解释器来执行或者解释


    2. Python解释器的种类:
        1、CPython  2、IPython 3、PyPy  4、Jython  5、IronPython


    3. 当前市场使用的最多(95%)的解释器就是CPython解释器


    4. GIL全局解释器锁是存在于CPython中


    5. 结论是同一时刻只有一个线程在执行? 想避免的问题是,出现多个线程抢夺资源的情况
        比如:现在起一个线程,来回收垃圾数据,回收a=1这个变量,另外一个线程也要使用这个变量a,当垃圾回收线程还没没有把变量a回收完毕,另一个线程就来抢夺这个变量a使用。
        怎么避免的这个问题,那就是在Python这门语言设计之处,就直接在解释器上添加了一把锁,这把锁就是为了让统一时刻只有一个线程在执行,言外之意就是哪个线程想执行,就必须先拿到这把锁(GIL), 只有等到这个线程把GIL锁释放掉,别的线程才能拿到,然后具备了执行权限.
        


    得出结论:GIL锁就是保证在统一时刻只有一个线程执行,所有的线程必须拿到GIL锁才有执行权限

    以下几个问题是需要理解记忆的


    1. python有GIL锁的原因,同一个进程下多个线程实际上同一时刻,只有一个线程在执行


    2. 只有在python上开进程用的多,其他语言一般不开多进程,只开多线程就够了


    3. cpython解释器开多线程不能利用多核优势,只有开多进程才能利用多核优势,其他语言不存在这个问题


    4. 8核cpu电脑,充分利用起我这个8核,至少起8个线程,8条线程全是计算--->计算机cpu使用率是100%


    5. 如果不存在GIL锁,一个进程下,开启8个线程,它就能够充分利用cpu资源,跑满cpu


    6. cpython解释器中好多代码,模块都是基于GIL锁机制写起来的,改不了了---》我们不能有8个核,但我现在只能用1核,----》开启多进程---》每个进程下开启的线程,可以被多个cpu调度执行


    7. cpython解释器:io密集型使用多线程,计算密集型使用多进程
        # -io密集型,遇到io操作会切换cpu,假设你开了8个线程,8个线程都有io操作---》io操作不消耗cpu---》一段时间内看上去,其实8个线程都执行了, 选多线程好一些


    # -计算密集型,消耗cpu,如果开了8个线程,第一个线程会一直占着cpu,而不会调度到其他线程执行,其他7个线程根本没执行,所以我们开8个进程,每个进程有一个线程,8个进程下的线程会被8个cpu执行,从而效率高


    计算密集型选多进程好一些,在其他语言中,都是选择多线程,而不选择多进程.

     

    互斥锁

    在多线程的情况下,同时执行一个数据,会发生数据错乱的问题

    1. n = 10
    2. from threading import Lock
    3. import time
    4. def task(lock):
    5. lock.acquire()
    6. global n
    7. temp = n
    8. time.sleep(0.5)
    9. n = temp - 1
    10. lock.release()

    拿时间换空间,空间换时间 时间复杂度

    1. from threading import Thread
    2. if __name__ == '__main__':
    3. tt = []
    4. lock=Lock()
    5. for i in range(10):
    6. t = Thread(target=task, args=(lock, ))
    7. t.start()
    8. tt.append(t)
    9. for j in tt:
    10. j.join()
    11. print("主", n)

    问题:既然有了GIL锁,为什么还要互斥锁? (多线程下)

    比如:我起了2个线程,来执行a=a+1,a一开始是0


           1. 第一个线程来了,拿到a=0,开始执行a=a+1,这个时候结果a就是1了


           2. 第一个线程得到的结果1还没有赋值回去给a,这个时候,第二个线程来了,拿到的a是0,继续执行
                a=a+1结果还是1


           3. 加了互斥锁,就能够解决多线程下操作同一个数据,发生错乱的问题

     

    线程队列

    queue队列:使用import queue,用法与进程Queue一样

    """
    同一个进程下多个线程数据是共享的
    为什么先同一个进程下还会去使用队列呢
    因为队列是
        管道 + 锁
    所以用队列还是为了保证数据的安全
    """

     

    先进先出 

    1. class queue.Queue(maxsize=0)
    2. import queue
    3. q=queue.Queue()
    4. q.put('first')
    5. q.put('second')
    6. q.put('third')
    7. print(q.get())
    8. print(q.get())
    9. print(q.get())
    10. '''
    11. 结果(先进先出):
    12. first
    13. second
    14. third
    15. '''

    进程Queue用于父进程与子进程(或同一父进程中多个子进程)间数据传递
    python自己的多个进程间交换数据或者与其他语言(如Java)进程queue就无能为力

    queue.Queue 的缺点是它的实现涉及到多个锁和条件变量,因此可能会影响性能和内存效率。

    后进先出

    1. class queue.LifoQueue(maxsize=0)
    2. import queue
    3. q=queue.LifoQueue()
    4. q.put('first')
    5. q.put('second')
    6. q.put('third')
    7. print(q.get())
    8. print(q.get())
    9. print(q.get())
    10. '''
    11. 结果(后进先出):
    12. third
    13. second
    14. first
    15. '''

     

    优先级队列

    1. import queue
    2. q=queue.PriorityQueue()
    3. #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
    4. q.put((20,'a'))
    5. q.put((10,'b'))
    6. q.put((30,'c'))
    7. print(q.get())
    8. print(q.get())
    9. print(q.get())
    10. '''
    11. 结果(数字越小优先级越高,优先级高的优先出队):
    12. (10, 'b')
    13. (20, 'a')
    14. (30, 'c')
    15. '''

    进程池和线程池的使用

    池:池子、容器类型,可以盛放多个元素

    进程池:提前定义好一个池子,然后,往这个池子里面添加进程,以后,只需要往这个进程池里面丢任务就行了,然后,有这个进程池里面的任意一个进程来执行任务

    线程池:提前定义好一个池子,然后,往这个池子里面添加线程,以后,只需要往这个线程池里面丢任务就行了,然后,有这个线程池里面的任意一个线程来执行任务


    进程池和线程池有什么好处呢? 

    1. def task(n, m):
    2. return n+m
    3. def task1():
    4. return {'username':'kevin', 'password':123}
    5. """开进程池"""
    6. from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    7. def callback(res):
    8. print(res) # Future at 0x1ed5a5e5610 state=finished returned int>
    9. print(res.result()) # 3
    10. def callback1(res):
    11. print(res) # Future at 0x1ed5a5e5610 state=finished returned int>
    12. print(res.result()) # {'username': 'kevin', 'password': 123}
    13. print(res.result().get('username'))
    14. if __name__ == '__main__':
    15. pool=ProcessPoolExecutor(3) # 定义一个进程池,里面有3个进程
    16. ## 2. 往池子里面丢任务
    17. pool.submit(task, m=1, n=2).add_done_callback(callback)
    18. pool.submit(task1).add_done_callback(callback1)
    19. pool.shutdown() # join + close
    20. print(123)

    协程

    一 猴子补丁

    1.1 什么是猴子补丁?

    1,这个词原来为Guerrilla Patch,杂牌军、游击队,说明这部分不是原装的,在英文里guerilla发音和gorllia(猩猩)相似,再后来就写了monkey(猴子)。

    2,还有一种解释是说由于这种方式将原来的代码弄乱了(messing with it),在英文里叫monkeying about(顽皮的),所以叫做Monkey Patch。

    名字听起来稀奇古怪的, 跟python的这个功能搭不上边, 所以我们直接来说功能吧!

     

    1.2 猴子补丁的功能(一切皆对象)

    1.拥有在模块运行时替换的功能, 例如: 一个函数对象赋值给另外一个函数对象(把函数原本的执行的功能给替换了)

    1. class Monkey():
    2. def play(self):
    3. print('猴子在玩')
    4. class Dog():
    5. def play(self):
    6. print('狗子在玩')
    7. m=Monkey()
    8. m.play()
    9. m.play=Dog().play
    10. m.play()

    1.3 monkey patch的应用场景

    这里有一个比较实用的例子,很多用到import json, 后来发现ujson性能更高,如果觉得把每个文件的import json改成import ujson as json成本较高, 或者说想测试一下ujson替换是否符合预期, 只需要在入口加上:

    1. import json
    2. import ujson
    3. def monkey_patch_json():
    4. json.__name__ = 'ujson'
    5. json.dumps = ujson.dumps
    6. json.loads = ujson.loads
    7. monkey_patch_json()
    8. aa=json.dumps({'name':'lqz','age':19})
    9. print(aa)

     

    二 Gevent介绍

    Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

     

    2.1 用法

    1. #用法
    2. g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
    3. g2=gevent.spawn(func2)
    4. g1.join() #等待g1结束
    5. g2.join() #等待g2结束
    6. #或者上述两步合作一步:gevent.joinall([g1,g2])
    7. g1.value#拿到func1的返回值

     

    2.2 示例1(遇到io自动切)

    1. import gevent
    2. def eat(name):
    3. print('%s eat 1' %name)
    4. gevent.sleep(2)
    5. print('%s eat 2' %name)
    6. def play(name):
    7. print('%s play 1' %name)
    8. gevent.sleep(1)
    9. print('%s play 2' %name)
    10. g1=gevent.spawn(eat,'lqz')
    11. g2=gevent.spawn(play,name='lqz')
    12. g1.join()
    13. g2.join()
    14. #或者gevent.joinall([g1,g2])
    15. print('主')

     

    2.3 示例2

    1. '''
    2. 上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,
    3. 而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
    4. from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前
    5. 或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头
    6. '''
    7. from gevent import monkey;monkey.patch_all()
    8. import gevent
    9. import time
    10. def eat():
    11. print('eat food 1')
    12. time.sleep(2)
    13. print('eat food 2')
    14. def play():
    15. print('play 1')
    16. time.sleep(1)
    17. print('play 2')
    18. g1=gevent.spawn(eat)
    19. g2=gevent.spawn(play_phone)
    20. gevent.joinall([g1,g2])
    21. print('主')
    22. # 我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程

     

    2.4 单线程的套接字服务端并发

    服务端

    1. from gevent import monkey;monkey.patch_all()
    2. import gevent
    3. from socket import socket
    4. # from multiprocessing import Process
    5. from threading import Thread
    6. def talk(conn):
    7. while True:
    8. try:
    9. data=conn.recv(1024)
    10. if len(data)==0:break
    11. print(data)
    12. conn.send(data.upper())
    13. except Exception as e:
    14. print(e)
    15. conn.close()
    16. def server(ip,port):
    17. server = socket()
    18. server.bind((ip, port))
    19. server.listen(5)
    20. while True:
    21. conn,addr=server.accept()
    22. # t=Process(target=talk,args=(conn,))
    23. # t=Thread(target=talk,args=(conn,))
    24. # t.start()
    25. gevent.spawn(talk,conn)
    26. if __name__ == '__main__':
    27. g1=gevent.spawn(server,'127.0.0.1',8080)
    28. g1.join()

     

    客户端

    1. import socket
    2. from threading import current_thread,Thread
    3. def socket_client():
    4. cli=socket.socket()
    5. cli.connect(('127.0.0.1',8080))
    6. while True:
    7. ss='%s say hello'%current_thread().getName()
    8. cli.send(ss.encode('utf-8'))
    9. data=cli.recv(1024)
    10. print(data)
    11. for i in range(500):
    12. t=Thread(target=socket_client)
    13. t.start()

  • 相关阅读:
    【跨境电商】全渠道客户服务终极指南(二):提供全渠道客户服务的 6 种方式
    核心实验17_IRF堆叠_H3C
    CTFhub-RCE-过滤cat
    TimeBasedRollingPolicy简介说明
    【STM32基础 CubeMX】外部中断
    Python 基础合集8:类的继承和多态
    小米poco x3 pro adbd 以root权限启动提供服务
    开发人员必备的万能工具箱:He3
    【八】文件_File_Imgae——read_image()、write_image()算子
    Softek Barcode Reader 9.1.5
  • 原文地址:https://blog.csdn.net/m0_71115526/article/details/133926344