GIL 线程池 进程池 同步 异步

Posted gengbinjia

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GIL 线程池 进程池 同步 异步相关的知识,希望对你有一定的参考价值。

1.GIL(理论 重点)
2.线程池 进程池
3.同步 异步


GIL
是一个全局解释器锁,是一个互斥锁
为了防止竞争解释器资源而产生的

为何需要gil:因为一个python.exe进程中只有一份解释器,如果这个进程开启了多个线程 都要执行代码
多线程之间要竞争解释器 一旦竞争就有可能出现问题

带来的问题:同一时间只有一个线程可以访问解释器
好处:保证了多线程的数据安全

thread-safe 线程安全的 多个线程同时访问也不会出问题
not thread-safe 非线程安全的 多个线程同时访问可能会出问题(加锁)


默认情况下一个进程只有一个线程 不会出现问题的 但是不要忘记还有GC线程
一旦出现多个线程就可能出问题 所以当初就简单粗暴的加上了GIL锁

如果你的应用程序是大量的IO操作 GIL的影响是微乎其微的
如果你的应用程序时一个大量计算操作 GIL的影响是非常大的 完全无法利用多核cpu

 


由于有GIL的存在 即使有多个cpu 也不能真正的并行

有三个任务 三个任务要并发执行 是效率最高
1.多进程
2.同一个进程下多线程

只有一个cpu
如果3个任务都要等待IO
如果是采用方案1:由于IO的时间较长 不仅不能提高效率 反而无无谓的增加了系统开销
方案2 更好

有三个cpu
如果是采用方案1 并且三个任务都没有IO操作:开启三个进程 并行的来执行 效率更高
如果是采用方案2 并且三个任务都没有IO操作:开启三个线程 必须串行执行 所以效率比进程更低

应用程序分为两种
1.IO密集型 IO操作较多 纯计算较少 采用多线程
技术图片
from multiprocessing import Process
from threading import  Thread,enumerate,current_thread

import time
def task():
    with open("2.昨日回顾","rt",encoding="utf-8") as f:
        f.read()


if __name__ == __main__:
    start = time.time()

    for i in range(100):
        Thread(target=task).start()

    # enumerate是所有的线程
    for t in enumerate():
        if t != current_thread():
            t.join()


    # ps = []
    # for i in  range(100):
    #     p = Process(target=task)
    #     p.start()
    #     ps.append(p)
    #
    # for p in ps:
    #     p.join()

    print(time.time()-start)
IO密集型任务

 


2.计算密集型 计算操作较多 IO较少 采用多进程
技术图片
from multiprocessing import Process
from threading import  Thread,enumerate,current_thread

import time
def task():
    with open("2.昨日回顾","rt",encoding="utf-8") as f:
        f.read()


if __name__ == __main__:
    start = time.time()

    for i in range(100):
        Thread(target=task).start()

    # enumerate是所有的线程
    for t in enumerate():
        if t != current_thread():
            t.join()


    # ps = []
    # for i in  range(100):
    #     p = Process(target=task)
    #     p.start()
    #     ps.append(p)
    #
    # for p in ps:
    #     p.join()

    print(time.time()-start)
计算密集型任务

 



应用场景:
TCP程序 应该采用多线程
纯计算 例如人脸识别 语音识别等 采取多进程

既然已经有锁了 还需要自己加锁吗?
什么情况下需要自己加锁 当多个线程需要共享一个不属于解释器资源时 必须要自己家

不加锁的例子:多个线程要并发修改某一个变量数据

from concurrent.futures import ThreadPoolExecutor

池就是容器
服务器不可能无限的开线程,所以需要对线程数量加以控制,线程池就是帮我么封装了线程数量的控制
以及线程的创建 销毁 任务的分配

使用方法一样的
线程池 在创建时 不会立即开启线程
等到提交任务时 如果没有空闲的线程 并且已存在的线程数量小于最大值 开个新的
线程开启以后就不会关闭了 直到进程全部结束为止
技术图片
from concurrent.futures import ThreadPoolExecutor
import threading


def task():
    print("running............")

pool = ThreadPoolExecutor(3)
pool.submit(task)
pool.submit(task)
pool.submit(task)
pool.submit(task)


print(threading.active_count())
print(threading.enumerate())
import  time

time.sleep(3)
print(threading.active_count())
print(threading.enumerate())
线程池特征

 


3.同步 异步 阻塞 非阻塞

阻塞:程勋运行过程中遇到IO操作 无法继续
非阻塞:程序正在运行中,并且没有遇到IO操作 即使遇到IO也不会阻塞,cpu不会切走

指的是程序的执行状态

指的是发起人武的方式
同步:
在发起任务后必须在原地等待 任务执行完毕 才能继续往下执行
异步:
在发起任务后立即继续往下执行 不需要等待任务的执行结果

异步效率高于同步
发起异步任务的方式 就是线程和进程

同步和阻塞是完全不同的:
阻塞一定是CPU已经切走了
同步虽然也会卡住 但是CPU没切走 还在你的进程中
技术图片
from concurrent.futures import  ThreadPoolExecutor

pool = ThreadPoolExecutor()

import time

def task(num):
    time.sleep(0.5)
    print("run.....")
    return num ** 2

ress = []

for i in range(10):
   res = pool.submit(task,i)
    # res.result() 该函数是阻塞 会一直等到任务执行完毕 导致程序串行执行
    ress.append(res)

# 保证 当我要获取的时候 所有任务都已经执行完毕
pool.shutdown(wait=True) # 该函数也是阻塞函数

# 等到全部完成在获取结果
for i in ress:
    print(i.result())

print("over") 
同步异步

 



pool.shutdown(wait=True)该函数也是阻塞函数 关闭线程池
等到全部完成在获取结果

以上是关于GIL 线程池 进程池 同步 异步的主要内容,如果未能解决你的问题,请参考以下文章

4月27日 python学习总结 GIL进程池线程池同步异步阻塞非阻塞

GIL 线程池与进程池 同步与异步

python第三十七天,GIL全局解释器锁*****,线程池与进程池 同步异步,阻塞与非阻塞,异步回调

27 Apr 18 GIL 多进程多线程使用场景 线程互斥锁与GIL对比 基于多线程实现并发的套接字通信 进程池与线程池 同步异步阻塞非阻塞

GIL锁,线程池,同步异步

并发编程--一堆锁,GIL,同步异步,Event事件