第三十八天 GIL 进程池与线程池

今日内容:

1.GIL 全局解释器锁

2.Cpython解释器并发效率验证

3.线程互斥锁和GIL对比

4.进程池与线程池

一.全局解释器锁

  1.GIL:全局解释器锁

    GIL本质就是一把互斥锁,是夹在解释器身上的

    统一进程内的所有线程都需要先抢到GIL锁,才能执行pai解释器代码

  2.GIL优缺点:

    优点:

扫描二维码关注公众号,回复: 4045205 查看本文章

      保证Cpython解释器内存管理的线程安全

    缺点:

      同一进程内所有的线程同一时刻只能有一个执行,

      也就是锁Cpython解释器多线程无法实现真正的并行

from threading import Thread,current_thread
import time

def task():
    print("%s is running"%current_thread().name)
    time.sleep(3)
    print("%s is done"current_thread().name)

if __name__=="__main__":
    t1=Thread(target=task)
    t2=Thread(target=task)
    t3=Thread(target=task)
    t1.start()
    t2.start()
    t3.start()

二.Cpython解释器并发效率验证

关于GIL性能的讨论

解释器加锁以后
将导致所有线程只能并发 不能达到真正的并行 意味着同一时间只有一个CPU在处理你的线程
给你的感觉是效率低

代码执行有两种状态
阻塞 i/o 失去CPU的执行权 (CPU等待IO完成)
非阻塞 代码正常执行 比如循环一千万次 中途CPU可能切换 很快会回来 (CPU在计算)

假如有32核CPU 要处理一个下载任务 网络速度慢 100k/s 文件大小为1024kb
如果你的代码中IO操作非常多 cpu性能不能直接决定你的任务处理速度


案例:
目前有三个任务 每个任务处理需一秒 获取元数据需要一小时
3个CPU 需要 一小时1秒
1个cpu 需要 一小时3秒


在IO密集的程序中 CPU性能无法直接决定程序的执行速度 python就应该干这种活儿
在计算密集的程序中 CPU性能可以直接决定程序的执行速度

#计算密集型测试
from
threading import Thread from multiprocessing import Process import time # 计算密集任务 def task1(): sum = 1 for i in range(10000000): sum *= i def task2(): sum = 1 for i in range(10000000): sum *= i def task3(): sum = 1 for i in range(10000000): sum *= i def task4(): sum = 1 for i in range(10000000): sum *= i def task5(): sum = 1 for i in range(10000000): sum *= i def task6(): sum = 1 for i in range(10000000): sum *= i if __name__ == '__main__': # 开始时间 st_time = time.time() # 多线程情况下 # t1 = Thread(target=task1) # t2 = Thread(target=task2) # t3 = Thread(target=task3) # t4 = Thread(target=task4) # t5 = Thread(target=task5) # t6 = Thread(target=task6) t1 = Process(target=task1) t2 = Process(target=task2) t3 = Process(target=task3) t4 = Process(target=task4) t5 = Process(target=task5) t6 = Process(target=task6) t1.start() t2.start() t3.start() t4.start() t5.start() t6.start() # # t1.join() # t2.join() # t3.join() # t4.join() # t5.join() # t6.join() print(time.time() - st_time)
from threading import Thread
from multiprocessing import Process
import time


# 计算密集任务
def task1():
    time.sleep(3)


def task2():
    time.sleep(3)


def task3():
    time.sleep(3)


def task4():
    time.sleep(3)


def task5():
    time.sleep(3)


def task6():
    time.sleep(3)

if __name__ == '__main__':

    # 开始时间
    st_time = time.time()
    # 多线程情况下
    # t1 = Thread(target=task1)
    # t2 = Thread(target=task2)
    # t3 = Thread(target=task3)
    # t4 = Thread(target=task4)
    # t5 = Thread(target=task5)
    # t6 = Thread(target=task6)


    t1 = Process(target=task1)
    t2 = Process(target=task2)
    t3 = Process(target=task3)
    t4 = Process(target=task4)
    t5 = Process(target=task5)
    t6 = Process(target=task6)

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()
    t6.start()

    # t1.join()
    # t2.join()
    # t3.join()
    # t4.join()
    # t5.join()
    # t6.join()

    print(time.time() - st_time)

三.GIL与互斥锁

from  threading import Thread,Lock
import time

mutex = Lock()
num = 1
def task():
    global num
    # print(num)
    mutex.acquire()
    temp = num
    print(temp)
    time.sleep(1)   # 当你们线程中出现io时 GIL锁就解开
    num = temp + 1
    mutex.release()  # 线程任务结束时GIL锁解开


t1 = Thread(target=task,)

t2 = Thread(target=task,)
t1.start()
t2.start()
t1.join()
t2.join()
print(num)

GIL 和自定义互斥锁的区别

全局锁不能保证自己开启的线程安全 但要保证解释器中的数据的安全

GIL 在线程调用解释器是 自动加锁 在IO阻塞时或线程执行完毕时 自动解锁

四.进程池和线程池

  进程池

    就是一个装进程的容器

  为什么出现

    当进程很多的时候方便管理进程

  什么时候用?

    当并发量特别大的时候 列入双十一

    很多时候进程是空闲的 就让他进入进程池 让有任务处理时才从进程取出来使用

  进程池使用

    ProcessPoolExecutor类

    创建时指定最大进程数 自动创建进程

    调用submit函数将任务提交进程池中

    创建进程是在调用submit后发生

  总结一下:

    进程池可以自动创造进程

    进程现在最大进程数

    自动选择一个空闲的进程帮你处理任务

  进程什么时候算是空闲?

    代码执行完算是空闲

  IO密集时 用线程池

  计算密集时 用线程池

作业:

1、整理GIL解释器锁,解释以下问题

1、什么是GIL

GIL = Global Interpreter Lock (全局解释锁器锁)
2、有了GIL会对单进程下的多个线程造成什么样的影响

防止对个线程竞争统一资源造成数据的错乱,只能有一个线程进行读写操作.
3、为什么要有GIL

为了避免资源竞争造成的数据错乱
4、GIL与自定义互斥锁的区别,多个线程争抢GIL与自定义互斥锁的过程分析

GIL不保证自己开启线程的安全 但保证解释器中数据的安全

GIL 在线程调用解释其时 自动加锁 在IO阻塞是或线程代码执行完毕时,自动解锁
5、什么时候用python的多线程,什么时候用多进程,为什么?

2、进程池与线程池
1、池的用途,为何要用它

池用来存储线程和进程,可以方便进程和线程的管理
2、池子里什么时候装进程什么时候装线程?

  计算密集时装进程,IO密集时装线程

3、基于进程池与线程池实现并发的套接字通信

#服务端

from socket import *
from threading import Thread
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor

tpool=ThreadPoolExecutor(3)

def communicte(conn,client_addr):
    while True:
        try:
            data= conn.recv(1024)
            if not data:break
            conn.send(data.upper())
        except ConnectionAbortedError:
            break
    conn.close()

def server():
    server = socket(AF_INET,SOCK_STREAM)
    server.bind(('127.0.0.1',8080))
    server.listen(5)

    while True:
        conn,client_addr=server.accept()
        print(client_addr)
        tpool.submit(communicte,conn,client_addr)
    server.close()

if __name__=='__main__':
    server()
#客户端
import socket

c = socket.socket()
c.connect(('127.0.0.1',8080))
while True:
    msg = input(">>>:")
    c.send(msg.encode("utf-8"))
    data = c.recv(1024)
    print(data.decode("utf-8"))

4、基于线程池实现一个可以支持并发通信的套接字,完成以下功能?
执行客户端程序,用户可选的功能有:
1、登录
2、注册
3、上传
4、下载

思路解析:
1、执行登录,输入用户名egon,密码123,对用户名egon和密码进行hash校验,并加盐处理,将密文密码发送到服务端,与服务端事先存好用户名与密文密码进行对比,对比成功后,
在服务端内存中用hash算法生成一个随机字符串比如eadc05b6c5dda1f8772c4f4ca64db110
然后将该字符串发送给用户以及登录成功的提示信息发送给客户端,然后在服务存放好
current_users={
'a3sc05b6c5dda1f8313c4f4ca64db110':{'uid':0,'username':'alex'},
'e31adfc05b6c5dda1f8772c4f4ca64b0':{'uid':1,'username':'lxx'},
'eadc05b6c5dda1f8772c4f4ca64db110':{'uid':2,'username':'egon'},

}

用户在收到服务端发来的'eadc05b6c5dda1f8772c4f4ca64db110'以及登录成功的提示信息后,以后的任何操作都会携带该随机字符串'eadc05b6c5dda1f8772c4f4ca64db110‘,服务端会根据该字符串获取用户信息来进行与该用户匹配的操作

在用户关闭连接后,服务端会从current_users字典中清除用户信息,下次重新登录,会产生新的随机字符串
这样做的好处:
1、用户的敏感信息全都存放到服务端,更加安全

2、每次登录都拿到一个新的随机的字符串,不容易被伪

2、执行注册功能,提交到服务端,然后存放到文件中,如果用户已经存在则提示用户已经注册过,要求重新输入用户信息

3、执行上次下载功能时会携带用户的随机字符串到服务端,如果服务端发现该字符串not in current_users,则要求用户先登录

猜你喜欢

转载自www.cnblogs.com/gongcheng-/p/9949165.html