【8.11】 并发编程-完结

今日内容概要

  • 验证GIL的存在
  • 验证python多线程是否有用
  • 死锁现象
  • 信号量
  • event事件
  • 进程池与线程池(重要)
  • 协程

今日内容详细

验证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)  #把运行的线程添加进去 [线程1 线程2 线程3 ... 线程100]
for t in t_list:
    t.join()
# 等待所有的线程运行结束 查看结果
print(money)  # 0

# 结果:
	  经验证确实有GIL存在  所以结果为0


验证GIL的特点

from threading import Thread
import time

money = 100


def task():
    global money
    tmp = money
    time.sleep(0.1)
    money =tmp - 1


t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)  # 把运行的线程添加进去 [线程1 线程2 线程3 ... 线程100]
for t in t_list:
    t.join()
# 等待所有的线程运行结束 查看结果
print(money)  # 99


# 原理:
	  在程序进入IO时  CPU会闲置 线程会自动去匹配CUP 每个线程都会进入IO 所有每个线程都会先拿到100 然后IO结束 按照代码程序去减一
"""
GIL不会影响程序层面的数据 同时也不会保证它的修改是安全的  想要得到保证 需要自己去加锁控制代码
"""
# 处理措施:在依然进入IO的情况下 让其结果还等于0  解锁控制

from threading import Thread,Lock
import time

money = 100
mutex = Lock()


def task():
    mutex.acquire()
    global money
    tmp = money
    time.sleep(0.1)
    money =tmp - 1
    mutex.release()


t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)  # 把运行的线程添加进去 [线程1 线程2 线程3 ... 线程100]
for t in t_list:
    t.join()
# 等待所有的线程运行结束 查看结果
print(money)  # 0

#原理:
	  在加锁之后 线程在进入程序后 先拿到锁后 进入IO 然后GIL会闲置 被其他线程占用 但是因为之前的线程没有解锁 所以拿到GIL没有用  必须等上一个线程释放锁之后 才能用 依次往返 每一个线程结果后 money都会少一  100个线程 代码走完  结果为0

验证python多线程是否有用

需要分情况
	情况1
    	单个CPU
    	多个CPU
	情况2
    	IO密集型(代码有IO操作)
       计算密集型(代码没有IO)
 
1.单个CPU
	IO密集型
    	多进程
        申请额外的空间 消耗更多的资源
		多线程
        消耗资源相对较少 通过多道技术
      ps:多线程有优势!!!
 	计算密集型
    	多进程
        申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
 		多线程
        消耗资源相对较少 通过多道技术(总耗时+切换)
      ps:多线程有优势!!!
2.多个CPU
	IO密集型
   		多进程
         总耗时(单个进程的耗时+IO+申请空间+拷贝代码)
       多线程
    	  总耗时(单个进程的耗时+IO)
       ps:多线程有优势!!!
	计算密集型
    	多进程
       	  总耗时(单个进程的耗时)
    	多线程
          总耗时(多个进程的综合)
       ps:多进程完胜!!!
 
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__':
    # print(os.cpu_count())  # 12  查看当前计算机CPU个数
    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
"""

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()
    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.6402878761291504
    多线程:0.0149583816528320
"""

死锁现象

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()
    
# 过程:(死锁现象)
	   代码启动时  线程一线先执行func1 正常结束后  执行func2 在抢到B锁之后 程序睡了一秒进入IO 线程二这时进入func1抢到A锁  当要抢B锁的时候 在线程一手里   线程一睡醒后  正常程序要走到抢A锁的时候 在线程二手里  这样也就锁住了  形成了死锁现象 

信号量

  • 信号量本质也是互斥锁 只不过它多了把锁
  • 强调:
    • 信号量在不同的知识体系中 意思可能有区别
      • 在并发编程中 信号量就是把互斥锁
      • 在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()

# 结果:
		正常情况下 一个进程中多个线程(上述是20)  打印情况通常是一个接着一个打印  但是加入信号量(上述设定为5把锁)  一次性就会打印5个线程

event事件

  • 子进程与子线程之间可以彼此等待彼此
  • 子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()

# 过程:
	    event.set()    结束
    	event.wait()   等待
     配合使用   wait 在等待 set执行  一旦执行  wait就会触发

进程池和线程池

  • 池:

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

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

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

    • 在实际应用中是不可以无限制的开进程和线程的 容易造成内存溢出 受限于硬件水平
    • 我们在开设多进程或者多线程的时候 还需要考虑硬件的承受范围
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import current_thread
import os
import time

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


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


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


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

协程

  • 进程:资源单位
  • 线程:执行单位
  • 协程:单线程下实现并发(效率极高)
    • 在代码层面欺骗CUP 让CUP觉得我们的代码里面没有IO操作
    • 实际上IO操作被我们自己写的代码检测 一旦有IO 立刻上代码执行别的(该技术完全是程序员自己弄出来的 名字也是程序员自己起的)
    • 核心:自己写代码完成 切换+ 保存状态
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()
    # func1()
    # func2()
    s1 = spawn(func1)  # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
    s2 = spawn(func2)
    s1.join()
    s2.join()
    print(time.time() - start_time)  # 8.01237154006958   协程 5.015487432479858

协程实现TCP服务端并发

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()

总结

  • 如何不断的提升程序的运行效率:
    • 多进程下多线程 多线程下开协程
posted @   W日常  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示