并发编程4

87 阅读6分钟

验证GIL

  • python解释器层面的一把锁

GIL的存在

    # 并发
from threading import Thread

num = 100


def task():
    global num
    num -= 1    # 通过global让num每次都-1


t_list = []
for i in range(100):  # 循环执行100个进程
    t = Thread(target=task)
    t.start()         # 开始执行
    t_list.append(t)  # 每次通过global获取的新数字添加到列表中保存

for t in t_list:      # 循环t_list内拿到的数
    t.join()          # 主线程等待子线程代码运行结束之后在往下运行
print(num)   # 0

"""
大致流程为:
    一百个进程抢一把锁,抢到之后执行进程操作,全局中的num每抢一次就自减一,
    当前进程运行完毕。其他进程又进行此操作,随后重复该操作
"""

GIL的特点

    # 并发
from threading import Thread
import time

num = 100


def task():
    global num
    tmp = num
    time.sleep(0.1)   # 进入IO操作放出GIL锁
    num = tmp - 1   


t_list = []
for i in range(100):  # 循环执行100个进程
    t = Thread(target=task)
    t.start()         # 开始执行
    t_list.append(t)  # 每次通过global获取的新数字添加到列表中保存

for t in t_list:      # 循环t_list内拿到的数
    t.join()          # 主线程等待子线程代码运行结束之后在往下运行
print(num)   # 99

"""
大致流程:
    一百个进程抢一把锁,抢到之后执行进程操作,全局中的num每抢一次先进行
IO操作,放出抢到的锁,其他进程又可以进行抢锁。每个程序睡眠结束后自减1,
没有锁,所以每次不会继承上一次的进程的num,得到的数仍为num-1之后的数。     
"""
"""
GIL不会影响程序层面的数据也不会保证它的修改是安全的要想保证得自己加锁
"""
    
    # 将上面的并发变为串行
from threading import Thread,Lock
import time

money = 100
mutex = Lock()


def task():
    mutex.acquire()   # 再给进程加一把互斥锁
    global money
    tmp = money
    time.sleep(0.1)   # 进行IO操作
    money = tmp - 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)

验证python的多线程有什么用

    1. 单个cpu的IO密集型
    • 多进程
      申请额外的空间,消耗的资源更多
      
    • 多线程
      资源消耗相比较少,通过多道技术
      
      • 此类型中多线程具有一定优势
    1. 单个cpu的计算密集型
    • 多进程
      申请额外的空间 消耗更多的资源
      
    • 多线程
      资源消耗相比较少,通过多道技术
      
      • 此类型中多线程具有一定优势
    1. 多个cpu的IO密集型
    • 多进程
      总耗时(单个进程的耗时+IO+申请空间+拷贝代码)
      
    • 多线程
      总耗时(单个进程的耗时+IO)
      
      • 此类型中多线程具有一定优势
          from threading import Thread
          from multiprocessing import Process
          import os
          import time 
          
          def work():
              time.sleep(2)   # 模拟纯IO操作
        
        
          if __name__ == '__main__':
              start_time = time.time()
                   # 多线程
              t_list = []
              for i in range(100):
                  t = Thread(target=work)
                  t.start()
              for t in t_list:
                  t.join()
              print('总耗时:%s' % (time.time() - start_time))
                  
                   # 多进程
              p_list = []
              for i in range(100):
                  p = Process(target=work)
                  p.start()
              for p in p_list:
                  p.join()
              print('总耗时:%s' % (time.time() - start_time))
                   
          """
          IO密集型
              多线程:0.0149583816528320
              多进程:0.6402878761291504
          """
        
    1. 多个cpu的计算密集型
    • 多进程
      总耗时(单个进程的耗时)
      
    • 多线程
      总耗时(多个进程的综合)
      
      • 此类型中的多进程优势很大
          from threading import Thread
          from multiprocessing import Process
          import os
          import time
        
        
          def work():
              # 计算密集型
              res = 1
              for i in range(1, 100000):
                  res *= i
        
        
          if __name__ == '__main__':
              start_time = time.time()
                   
                   # 多进程
              p_list = []
              for i in range(12):  # 一次性创建12个进程
                  p = Process(target=work)
                  p.start()
                  p_list.append(p)
              for p in p_list:  # 确保所有的进程全部运行完毕
                  p.join()
                   
                   # 多线程
              t_list = []
              for i in range(12):
                  t = Thread(target=work)
                  t.start()
                  t_list.append(t)
              for t in t_list:
                  t.join()
              print('总耗时:%s' % (time.time() - start_time))  # 获取总的耗时
        
          """
          计算密集型
              多进程:5.665567398071289
              多线程:30.233906745910645
          """
        

死锁现象

  • 为什么会出现死锁现象?
    • 在抢锁的时候 通过一些IO操作形成了闭环,互拿对方的锁
    Lock()  # 类名加括号每执行一次就会产生一个新的对象
    
  • 实例分析
      from threading import Thread, Lock
      import time
    
      mutexA = Lock() 
      mutexB = Lock()  
    
    
      class MyThread(Thread):
          def run(self):
              self.func1()
              self.func2()
    
      def func1(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 func2(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()
    

信号量

  • 本质
    • 互斥锁,是多把互斥锁
      在并发编程中    信号量就是多把互斥锁
      在django中     信号量指的是达到某个条件自动触发(中间件)
      
    • Lock产生的是一把锁,信号量产生的是多把锁
  • 如何使用
      from threading import Thread, Lock, Semaphore
      import time
      import random
    
    
      sp = Semaphore(5)     # 一次性产生五把锁
    
    
      class MyThread(Thread):
          def run(self):
              sp.acquire()
              print(self.name)
              time.sleep(random.randint(1, 3))
              sp.release()
    
    
      for i in range(20):
          t = MyThread()
          t.start()
    

event事件

 子进程或者子线程之间可以彼此等待彼此
     eg:子A运行到某一个代码位置后发信号告诉子B开始运行
    
    from threading import Thread, Event
    import time

    event = Event()  # 类似于造了一个红绿灯


    def light():
        print('红灯亮 不能动')
        time.sleep(3)
        print('绿灯亮 可以动')
        event.set()


    def car(name):
        print('%s正在等红灯' % name)
        event.wait()
        print('%s车辆行驶了' % name)


    t = Thread(target=light)
    t.start()
    for i in range(20):
        t = Thread(target=car, args=('熊猫PRO%s' % i,))
        t.start()    
 

进程池与线程池

  • 为什么要有进程/线程池呢?
    • 为了保证计算机硬件的安全,但是代价是降低了程序的执行效率。我们需要考虑硬件的承受能力
  • 进程池提前创建好固定个数的进程供程序使用,后续不会再创建
  • 线程池提前创建好固定个数的线程供程序使用,后续不会再创建
实操:
    from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
    from threading import current_thread
    import os
    import time

    pool = ProcessPoolExecutor(5)  # 产生五个线程

    def task(n):
        print(os.getpid())
        time.sleep(1)
        return '返回结果'

    def func(*args, **kwargs):
        print('func', args, kwargs)
        print(args[0].result())

    if __name__ == '__main__':
        for i in range(20):
            pool.submit(task, 123).add_done_callback(func) 
            #异步回调:异步任务执行完成后有结果就会自动触发该机制
            

协程及协程实操

协程

  • 是程序员自己定义出出来的一种方法,可以在单线程下实现并发,效率极高
    • 其实就是戏法,让cpu误以为没有IO操作。实际上IO操作被我们自己写的代码检测,有的话立刻让代码执行别的。
    • 核心:自己写代码完成切换+保存状态
      from gevent import monkey;monkey.patch_all()      # 固定编写 用于检测所有的IO操作(猴子补丁)
      from gevent import spawn
    
      import time
      from gevent import monkey;
    
      monkey.patch_all()      # 固定编写 用于检测所有的IO操作(猴子补丁)
      from gevent import spawn
    
    
      def func1():
          print('func1 running')
          time.sleep(3)
          print('func1 over')
    
    
      def func2():
          print('func2 running')
          time.sleep(5)
          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)  # 8.01237154006958   协程 5.015487432479858
    

协程实操

实操:
import socket
from gevent import monkey;monkey.patch_all()  # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn


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


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)

s1 = spawn(get_server)
s1.join()