验证GIL的存在与特点、验证python多线程性能、死锁现象以及进程池线程池等

验证GIL的存在

from threading import Thread

money =100
def task():
    global money
    money -=1

t_list = []  # 存储每个线程对象
for i in range(100):
    t= Thread(target=task)
    t.start()
    t_list.append(t)
for t in t_list:
    t.join()  # 100个线程都开启之后在使用join方法,确保所有线程结束在打印money

print(money)  
image-20201212190410230

验证GIL的特点

from threading import Thread
import time

money =100
def task():
    global money
    temp = money
    time.sleep(1)  # IO会导致所有的线程在依次抢到GIL锁后也要被迫释放
    money=temp-1

t_list = []
for i in range(100):
    t= Thread(target=task)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()  # 等待所有线程运行结束在打印money

print(money)  # 99


from threading import Thread,Lock
import time
money=100
mutex=Lock()

def task():
    mutex.acquire()  # 抢锁
    global money
    temp=money
    time.sleep(0.02)
    money=temp-1
    mutex.release()  # 放锁

t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()

print(money)   # 0

验证python多线程是否有用

需要分以下几种情况

情况一:

​ 单个CPU

​ 多个CPU

情况二:

​ IO密集型(代码有IO操作)

​ 计算密集型(代码没有IO)

  1. 单个CPU
IO密集型
    多进程:申请额外的空间,消耗更多的资源
    多线程:消耗资源相对较少,通过多道技术
计算密集型
    多进程:申请额外的空间,消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
    多线程:消耗资源相对较少,通过多道技术(总耗时+切换)

由上可以看出在单U情况下无论是IO密集型还是计算密集型,多线程都更加有优势

  1. 多个CPU
IO密集型:
    多进程:总耗时(单个进程的耗时+申请空间+拷贝代码)
    多线程:总耗时(单个进程的耗时+IO)
计算密集型
    多进程:总耗时(单个进程的耗时)
    多线程:总耗时(多个进程的综合耗时)

当IO密集型时,多线程有优势,当计算密集型时,多进程更有优势

代码演示

import os
from multiprocessing import Process
from threading import Thread
import time

# 计算密集型
def work():
    res=1
    for i in range(1,100000):
        res *=i

if __name__ == '__main__':
    # print(os.cpu_count())  # 8 ,所以创建8个进程
    start_time = time.time()
    p_list = []
    # t_list = []
    for i in range(8):
        p = Process(target=work)
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()
    #     t= Thread(target=work)
    #     t.start()
    #     t_list.append(t)
    # for t in t_list:
    #     t.join()

    print('总耗时: %s'% (time.time()-start_time))

"""
计算密集型
    多进程: 总耗时>>> 5.5769689083099365
    多线程:总耗时>>> 22.86092185974121
"""

def work():
    time.sleep(2)

if __name__ == '__main__':
    start_time= time.time()
    # t_list = []
    p_list = []
    for i in range(100):
        # t = Thread(target=work)
        # t.start()
        # t_list.append(t)
    # for t in t_list:
    #     t.join()
        p = Process(target=work)
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()
    print('总耗时>>> %s' % (time.time()-start_time))

"""
计算密集型
    总耗时>>> 2.010854721069336
    总耗时>>> 3.6308200359344482
"""

死锁现象

定义:指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁

虽然我们已经掌握了互斥锁的使用(先抢锁,后释放锁),但是在实际项目中也应该尽量少用,因为用的不好,会产生死锁现象(如下代码所示)

from threading import Thread,Lock
import time
mutexA = Lock()  # 锁A
mutexB = Lock()  # 锁B
class MyThread(Thread):
    def run(self):
        self.fun1()
        self.fun2()

    def fun1(self):
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        mutexB.release()
        print(f'{self.name}释放了B锁')
        mutexA.release()
        print(f'{self.name}释放了A锁')
    def fun2(self):
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        time.sleep(1)
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexA.release()
        print(f'{self.name}释放了A锁')
        mutexB.release()
        print(f'{self.name}释放了B锁')

for i in range(10):
    t = MyThread()
    t.start()
"如下图所示,发生了死锁现象"

image

信号量

信号量本质也是互斥锁,只不过它是多把锁
信号量在不同的知识体系中,意思也可能有区别

  • 在并发编程中,信号量就是多把互斥锁
  • 在django中,信号量指的是达到某个条件自动触发(中间件)

我们之前使用Lock产生的是单把锁,信号量是多把锁

from threading import Thread,Semaphore
import time
import random

sp = Semaphore(5)  # 一次性产生5把互斥锁

class MyThread(Thread):
    def run(self):
        sp.acquire()
        print(f'{self.name}抢到了锁')
        time.sleep(random.random())
        sp.release()
        print(f'{self.name}释放了锁')

for i in range(20):
    t = MyThread()
    t.start()

event事件

event事件是指子进程或子线程之间可以彼此等待彼此

例如:子线程A 运行到某一个代码位置发送信告诉子线程B开始运行

import time
from threading import Thread,Event
event=Event()
def light():
    print('红灯亮着的,所有车辆不准通行')
    time.sleep(2)
    event.set()
def car(name):
    print(f'{name}正在等红灯')
    event.wait()
    print(f'{name}加油门,飙车了')

t= Thread(target=light)
t.start()
for i in range(20):
    t = Thread(target=car,args=(f'第{i}辆车',))
    t.start()

进程池和线程池

受限于硬件水平的原因,所以我们在实际应用中是不可以无限制的开进程和线程,
我们开设多进程和多线程的时候,还要考虑硬件的承受范围,在实际开发过程中,我们可以通过使用池来保证计算机硬件和程序的稳定。

  • 池:降低程序的执行效率,保证计算机硬件的安全

  • 进程池:提前创建好固定个数的进程供程序使用,后续不会再创建

  • 线程池:提前创建好固定个数的线程供程序使用,后续不会再创建

from concurrent.futures import ThreadPoolExecutor
from threading import current_thread
import time

pool = ThreadPoolExecutor(5)  # 产生一个固定只有5个线程的线程池
# 线程执行函数
def task():
    time.sleep(1)
    return f'{current_thread().name}结束了任务'
# 回调函数
def func(*args,**kwargs):
    print(args)  # (<Future at 0x7f7dad291e80 state=finished returned str>,)
    print(args[0].result())  # 打印线程的回调结果

for i in range(100):
    pool.submit(task).add_done_callback(func)   
"""
pool.submit(task): 朝池子中提交任务(异步)
add_done_callback(func):异步回调,自动获取线程执行函数的返回值,并传到回调函数func内
"""

# 进程的代码也是如此

协程

进程是资源单位,线程是执行单位,而协程是在单线程下实现并发(效率极高)

协程的实现原理是在代码层面欺骗CPU,让CPU觉得我们的代码里面没有IO操作,在实际上IO操作被我们自己写的代码检测,一旦有,立刻让代码执行别的(该技术完全是程序员自己弄出来的,名字也是程序员自己起的)

协程的核心就是自己写代码完成切换+保存状态

from gevent import monkey;monkey.patch_all()
from gevent import spawn
import time
def func1():
    print('func1 running')
    time.sleep(2)
    print('func1 over')

def func2():
    print('func2 running')
    time.sleep(4)
    print('func2 over')

if __name__ == '__main__':
    start_time = time.time()
    s1 = spawn(func1)  # 检测代码,一旦有IO自动切换程序(执行没有IO的操作,变向的等待IO结束)
    s2 = spawn(func2)
    s1.join()
    s2.join()
    print(time.time()-start_time)

协程实现TCP服务端并发

import socket
from gevent import monkey;monkey.patch_all()  # 固定编写,用于检测代码中所有的IO操作(猴子补丁)
from gevent import spawn
def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    while True:
        sock,addr = server.accept()  # io操作
        spawn(communication,sock)

def communication(sock):
    while True:
        data = sock.recv(1024)
        print(data.decode('utf8'))
        sock.send(data.upper())

p=spawn(get_server)
p.join()
posted @ 2022-08-11 18:44  荀飞  阅读(33)  评论(0编辑  收藏  举报