GIL全局解释器锁,互斥锁,进程池和线程池的用法,协程

目录

GIL全局解释器锁

互斥锁

线程队列

进程池和线程池的用法

协程理论

如何使用协程

基于协程的高并发程序


GIL全局解释器锁

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

访问python解释器由GIL全局解释器锁来控制,正是GIL全局解释器锁,所以程序中才能保证同一时刻只能有一个线程在运行

背景信息:

        1. Python代码运行在python解释器上,由python解释器来执行或解释

        2. Python解释器的种类:

                     1. CPython(用的最多)    2. IPython    3. pypy     4. JPython    5. TronPython

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

        4. 结论是:同一时刻只有一个线程在执行。 避免的问题:出现多个线程抢夺资源的情况

如何解决出现多个线程抢夺资源的情况?

        python语言在设计时,就添加了一把锁,这把锁就是为了同一时刻只有一个线程在执行,言外之意就是哪个线程想执行,就必须先拿到这把锁(GIL),只有等到这个线程把GIL锁释放掉,别的进程才能拿到,然后具备执行权限

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

关于GIL锁的结论:

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

        2. 只有python上开进程用的都,其他语言(其它语言没有GIL锁)一般不开多进程,只开多线程。

        3. cpython解释器开多线程不能利用多核优势,而是得利用多进程。其它语言没有这个问题

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

        5. cpython中很多代码都是基于GIL锁写的,电脑中只能使用一核,开启多进程,每个进程下开启一个线程,这样就可以被cpu充分调用执行


        6. CPython解释器:io密集型使用多线程,计算密集型使用多线程

                

                原因:1.      io密集型,遇到io操作会切换cpu。假如你开了8个线程,每个线程都有io操作----但是io操作不消耗cpu-----一段时间内看上去8个线程都执行了,因此选多线程

                2.   计算密集型:消耗cpu,如果开了8个线程,第一个线程会一直占用cpu,而不会调度到其它线程执行,其它7个线程根本没有执行,所以我们开了8个进程,每一个进程有有一个线程,8个进程下的线程会被8个cpu执行,从而效率高,因此选多进程

互斥锁

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

互斥锁作用:在多线程的情况下,同时执行一个数据,不会发生数据错乱的问题

互斥锁的使用:互斥锁的使用和进程锁一样,只是一个在进程中,一个在线程中

补充:

        面试题:既然有了GIL锁,为什么还要互斥锁?

                原因:线程的执行是非常快的,当第一个线程执行完毕后,还未结果返回,第二个线程就已经执行了,这样就会导致数据错乱,而互斥锁就可以很好的解决多线程下操作同一数据发生错乱的问题

        

线程队列(线程中使用队列)

        线程使用队列直接导入queue模块(import queue),然后调用Queue这个类(queue.Queue)

        

线程队列的三种特征:

        1. 先进先出(queue.Queue)

                使用方法:调用queue模块中的Queue类,然后入队和出队

        2. 后进先出(queue.LifoQueue)

                使用方法:调用queue模块中的LifoQueue类,然后入队和出队

        3. 优先级队列:(queue.priorityQueue)

                使用方法:调用queue模块中的priorityQueue类,然后入队和出队

queue.Queue的缺点:他涉及到多个锁和条件变量,因此可能会影响性能好内存效率

进程池和线程池

池:就是一个容器,可以存放多个元素

        

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

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

线程池

from concurrent.futures import ThreadPoolExecutor

pool = ThreadPoolExecutor(5)

括号内不传参数的话,会默认开设当前计算机cpu核数的5倍的线程(看源码即懂),假设参数写5,代表池子里面固定有5个线程,不存在不断创建和销毁的过程。

线程池的使用

进程池

from concurrent.futures import ProcessPoolExecutor
pool = ProcessPoolExecutor(3)

进程池大体上跟线程池类似,ProcessPoolExecutor(3),括号内不填的话,会默认创建与“cpu核数”相同数量的进程(看源码即懂),同样的,进程池中的进程是固定工,不会重复创建和销毁。

进程池和线程池在使用形式上是一样的,唯一不同的是:在Windows环境下,进程池要放在main方法里面,否则会报错

进程池的使用

协程理论

进程:资源分配的基本单位

线程:执行的最小单位

协程:单线程下的并发,最节省资源

并发的本质:切换+保存状态

        **以前的并发其实线程或进程的切换**

如何切换:

        切换是由程序员自己来切,不是操作系统切。本质上就是最大限度的利用cpu资源

如何使用:导入gevent模块,gevent模块不是内置模块因此需要我们下载。

如何下载:在pycharm的终端(Terminal)中输入pip install gevent 然后点回车

协程的优缺点以及特点

对比操作系统控制线程的切换,用户在单线程内控制协程的切换。

优点如下:

  1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
  2. 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:

  1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
  2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

协程实现高并发

        服务端代码
from gevent import monkey;

monkey.patch_all()
import gevent
from socket import socket
# from multiprocessing import Process
from threading import Thread


def talk(conn):
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0: break
            print(data)
            conn.send(data.upper())
        except Exception as e:
            print(e)
    conn.close()


def server(ip, port):
    server = socket()
    server.bind((ip, port))
    server.listen(5)
    while True:
        conn, addr = server.accept()
        # t=Process(target=talk,args=(conn,))
        # t=Thread(target=talk,args=(conn,))
        # t.start()
        gevent.spawn(talk, conn)


if __name__ == '__main__':
    g1 = gevent.spawn(server, '127.0.0.1', 8080)
    g1.join()
 客户端代码
import socket
from threading import current_thread, Thread


def socket_client():
    cli = socket.socket()
    cli.connect(('127.0.0.1', 8080))
    while True:
        ss = '%s say hello' % current_thread().getName()
        cli.send(ss.encode('utf-8'))
        data = cli.recv(1024)
        print(data)


for i in range(5000):
    t = Thread(target=socket_client)
    t.start()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值