python GIL锁、进程池与线程池、同步异步

一、GIL全局解释器锁

  • 全局解释器锁

    在CPython中,全局解释器锁(GIL)是一个互斥锁,它可以防止多个本机线程同时执行Python代码。之所以需要这个锁,主要是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在以来,其他特性已经逐渐依赖于它所执行的保证)

    • 什么是GIL

      全局解释器锁, 施加在解释器上的互斥锁

    • 为什么需要GIL

      由于CPython的内存管理时非线程安全,于是CPython就给解释器加上锁, 解决了安全问题.

  • GIL的加锁与解锁时机

    • 加锁的时机: 在调用解释器时立即加锁

    • 解锁的时机:

      • 当前线程遇到了IO时

      • 当前线程执行时间超过设定值时 , 一旦达到某个阈值 , CPU会通知线程保存状态切换线程 , 以此来保证数据安全

  • 总结 : 由于GIL锁的特性 , 我们需要考虑什么情况下用多线程什么情况下用多进程

    • 在单核情况下 , 无论是IO密集型还是计算密集型 , GIL都不会产生影响,多线程会更加经济实惠.

    • 在多核情况下 , IO密集型会受到GIL的影响

      • 对于计算密集型 , 需要并行处理 , 所以需要用到多进程

      • 对于IO密集型 , 由于IO时间较长 , 创建进程不经济 , 所以应该用多线程

二、GIL带来的问题

  • GIL的优缺点:

    • 优点:保证Cpython解释器内存管理的线程安全

    • 缺点: ​ 同一进程内所有的线程同一时刻只能有一个执行, ​ 也就说Cpython解释器的多线程无法实现并行

三、线程池和进程池

  • 什么是进程/线程池

    池表示一个容器 , 那么进程或线程池表示的就是一个存放进程或线程的列表

  • 什么时候使用线程池/进程池

    如果是IO密集型任务使用线程池 , 如果是计算密集型任务则使用进程池

  • 多线程Tcp通信

  服务端

from concurrent.futures import ThreadPoolExecutor
from threading import Thread
import socket
​
server = socket.socket()
server.bind(("127.0.0.1",8806))
server.listen()
​
# 创建线程池 指定最大线程数为3  如果不指定 默认为CPU核心数 * 5
pool = ThreadPoolExecutor(3)  # 不会立即开启子线程
def task(client):
    while True:
        try:
            data = client.recv(1024)
            if not data:
                client.close()
                break
            client.send(data.upper())
        except Exception:
            client.close()
            break
            
while True:
    client, addr = server.accept()
    # submit:提交任务到线程池,第一次提交任务时会创建进程  ,后续再提交任务,直接交给以及存在的进程来完成,如果没有空闲进程就等待
    pool.submit(task, client)  
View Code
  客户端
from threading import Thread
import socket
​
c = socket.socket()
c.connect(("127.0.0.1",8806))
​
def send_msg():
    while True:
        msg = input(">>>:")
        if not msg:
            continue
        c.send(msg.encode("utf-8"))
​
send_t = Thread(target=send_msg)
send_t.start()
​
while True:
    try:
        data = c.recv(1024)
        print(data.decode("utf-8"))
    except:
        c.close()
        break
View Code
 

四、自定义的线程锁与GIL的区别

GIL保护的是解释器级别的数据安全,比如对象的引用计数,垃圾分代数据等等,具体参考垃圾回收机制详解。

自定义线程锁保证的是进程内的资源在同一时间只能有一个线程去访问,保护的是进程内资源数据安全

四、同步与异步

阻塞与非阻塞:

阻塞 : 程序遇到IO操作,无法继续执行代码 , 叫做阻塞

非阻塞 : 没有阻塞 , 正常运行

同步与异步

提交任务的两种方式

  • 同步:

    发起任务后必须等待任务结束,拿到一个结果才能继续运行

  • 异步 : 提交任务后,不再原地等待,直接执行下一行代码,(执行结果?)

    异步效率高于同步

使用进程池 , 来实现异步任务

# 异步提交
from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor()  # 不设置线程池最大数量默认为CPU核心数*5
​
​
def task(i):
​
    time.sleep(1)
    print("sub thread run..")
    i += 100
    return i
​
fs = []
for i in range(10):
    f = pool.submit(task,i) # submit就是一异步的方式提交任务,会返回一个对象,通过调用result方法获得结果
    fs.append(f)  # 将submit对象添加到列表中
​
​
​
# 是一个阻塞函数,会等到池子中所有任务完成后继续执行
pool.shutdown(wait=True)  # 任务运行结束拿到结果后才会执行下一步
# pool.submit(task,1) # 注意 在shutdown之后 就不能提交新任务了
for i in fs:
    print(i.result())  # 循环取执行结果
print("over")
​
​
# 将程序改为同步提交
from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor()  # 不设置线程池最大数量默认为CPU核心数*5
​
​
def task(i):
​
    time.sleep(1)
    print("sub thread run..")
    i += 100
    return i
​
for i in range(10):
    f = pool.submit(task,i) # submit就是一异步的方式提交任务,会返回一个对象,通过调用result方法获得结果
    res = f.result()  # result是一个阻塞函数,不拿到结果不执行下一步
print(res) 
​
print("over")
View Code

同步提交任务 , 程序卡住 , 不一定是阻塞,因为任务中可能在做一堆计算任务 , CPU没走开。

猜你喜欢

转载自www.cnblogs.com/liusijun113/p/10217622.html