Python(GIL、死锁、信号量、event、协程)详细案例介绍

一、验证GIL的存在

		from threading import Thread
		money = 100

		def task():
		    global money        # 修改主线程的money
		    money -= 1          # 每次运行money - 1     没有io操作直接运行
		
		t_list = []             # 设置一个空列表存放线程
		for i in range(100):
		    t = Thread(target=task)
		    t.start()           # 如果所有线程同时运行则全部拿到100 结果则是 99
		    t_list.append(t)  	# [线程1 线程2 线程3 ... 线程100]
		for t in t_list:
		    t.join()
		print(money)        	# 等待所有的线程运行结束 查看money是多少
		
		输出结果: 0 

二、验证GIL的特点

		from threading import Thread
		import time
		money = 100
			
		def task():
		    global money
		    tmp = money             # 当GIL进入IO操作时会自动释放GIL互斥锁
		    time.sleep(0.1)         # 释放之后 后面的99个进程都能拿到GIL
		    money = tmp - 1         # 都拿到得数据都是100 减去1 还是 99	
		
		t_list = []             	# 设置一个空列表存放线程
		for i in range(100):
		    t = Thread(target=task)
		    t.start()           	# 如果所有线程同时运行则全部拿到100 结果则是 99
		    t_list.append(t)  		# [线程1 线程2 线程3 ... 线程100]
		for t in t_list:
		    t.join()
		print(money)        		# 等待所有的线程运行结束 查看money是多少

		输出结果: 99
		'''
		所以我们得出结论 GIL不会影响程序层面的数据 也不会保证它的修改是安全的 
		如果想要保证我们得加上 互斥锁!! (互斥锁具体看上一遍文章)
		'''
	解决方法:
		from threading import Thread, Lock      
		import time                # 导入互斥锁模块
		
		money = 100
		mutex = Lock()             # 设置互斥锁
		
		def task():
		    mutex.acquire()         # 抢互斥锁锁
		    global money
		    tmp = money             # 当GIL进入IO操作时会自动释放GIL互斥锁
		    time.sleep(0.1)         # 释放之后 后面的99个进程都能拿到GIL
		    money = tmp - 1         # 都拿到得数据都是100 减去1 还是 99
		    mutex.release()         # 释放互斥锁

		t_list = []             	# 设置一个空列表存放线程
		for i in range(100):
		    t = Thread(target=task)
		    t.start()           	# 如果所有线程同时运行则全部拿到100 结果则是 99
		    t_list.append(t)  		# [线程1 线程2 线程3 ... 线程100]
		for t in t_list:
		    t.join()
		print(money)        		# 等待所有的线程运行结束 查看money是多少

三、死锁现象

		到现在我们已掌握了互斥锁的使用 先抢锁再释放锁 但是实际项目很少用
		mutexA = Lock()  # 类名加括号每执行一次就会产生一个新的对象A
		mutexB = Lock()  # 类名加括号每执行一次就会产生一个新的对象B
		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锁')      # 这时候A锁还没有释放 所以只有第一个人抢得到b锁
		        mutexB.release()
		        print(f'{self.name}释放了B锁')      # 释放b锁 但是所有人还在外面没人抢得到
		        mutexA.release()
		        print(f'{self.name}释放了A锁')      # 这时候释放A锁 第二个人就可以抢到A锁了
		
		    def func2(self):
		        mutexB.acquire()
		        print(f'{self.name}抢到了B锁')      # 第一个人拿到 B锁
		        time.sleep(1)                      # 进入IO操作
		        mutexA.acquire()
		        print(f'{self.name}抢到了A锁')      # 这个时候发生了问题 目前A锁还没有释放!
		        mutexA.release()
		        print(f'{self.name}释放了A锁')      # 所以BUG
		        mutexB.release()
		        print(f'{self.name}释放了B锁')
		
		for i in range(10):
		    t = MyThread()	
		    t.start()	
		'''输出结果: A>B>B>A>B>A 这就是死锁现象卡在那'''

四、信号量

		信号量本质也是互斥锁 只不过他是多把互斥锁(自己可以设置数量)
		信号量在不同的知识体系中 意思是有区别的
		在并发编程中 信号量就是多把互斥锁
		而在Djiango中 信号量是指达到了某个条件自动触发
		我们之前使用Lock产生的是单把锁 信号量相当于一次性创建了N把锁
		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))        # 随时等待睡眠1——3秒
		        sp.release()            # 释放锁
		
		
		for i in range(10):             # 产生10个子进程
		    t = MyThread()
		    t.start()

五、event事件

		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(10):
		    t = Thread(target=car, args=('LebronJames%s' % i,))
		    t.start()

六、进程池和线程池

		我们在实际应用中是不是可以无限制的开进程和线程?
			肯定是不可以的 会造成内存溢出受限于硬件水平
			我们在开设多进程或者多线程的时候 还需要考虑硬件的承受范围

		池
			降低程序的执行效率 保证计算机硬件的安全
		进程池
			提前创建好固定个数的进程供程序使用 后续不会再创建
		线程池
			提前创建好固定个数的线程供程序使用 后续不会再创建
	# 线程
		from concurrent.futures import ThreadPoolExecutor
		from threading import current_thread
		import os
		import time
		
		pool = ThreadPoolExecutor(5)  # 固定产生五个线程
		def task():			   # 括号内可以加参
		    print(current_thread().name)	# 打印线程名称
		    time.sleep(1)			

		for i in range(10):	   # submit(函数名,实参1,实参2,...)
		    pool.submit(task)  # 可以跟着后面传参数逗号隔开
		    
		输出结果:ThreadPoolExecutor-0_0 ThreadPoolExecutor-0_1
				 ThreadPoolExecutor-0_2 ThreadPoolExecutor-0_3
				 ThreadPoolExecutor-0_4 ThreadPoolExecutor-0_0
				 ThreadPoolExecutor-0_2 ThreadPoolExecutor-0_3
				 ThreadPoolExecutor-0_1 ThreadPoolExecutor-0_4
				 
				 '''十个线程 都是相同的不会有新的这就是设置的五个线程'''

	# 进程
		from concurrent.futures import ProcessPoolExecutor
		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(10):
		        """异步回调:异步任务执行完成后有结果就会自动触发该机制"""
		        pool.submit(task, 123).add_done_callback(func)
		        
		输出结果:67697 67696 67698 67699 67700
				67696 func (<Future at 0x7f7c683816a0 state=finished returned str>,) {}	返回的结果
				67697 func (<Future at 0x7f7c683757c0 state=finished returned str>,) {}	返回的结果
				67698 func (<Future at 0x7f7c68381b20 state=finished returned str>,) {}	返回的结果
				......

七、协程

		我们都知道进程是资源单位
				线程是执行单位
				协程是单线程下实现并发(效率极高)
					在代码层面欺骗CPU 让CPU觉得我们的代码里面没有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()             	# 8.015673160552979秒
		    s1 = spawn(func1)  		# 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
		    s2 = spawn(func2)  		# 两个之间一直切换让CPU感觉一直在运行代码
		    s1.join()		   		# 这个时候进入IO一直切换
		    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)          # IO操作接收数据
			        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)      # 一旦有用户进来就启动 如果没有IO操作等着
			
			s1 = spawn(get_server)                  # 这样就好了 平凡的切换等着用户进来
			s1.join()

	客户端
		import socket
		from threading import Thread, current_thread
		
		def get_client():
		    client = socket.socket()
		    client.connect(('127.0.0.1', 8080))
		    while True:
		        client.send(f'hello baby {current_thread().name}'.encode('utf8'))
		        data = client.recv(1024)
		        print(data.decode('utf8'))
		
		for i in range(1000):		# 一人接待1000人!!!
		    t = Thread(target=get_client)
		    t.start()

技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请点点赞收藏+关注 谢谢支持 !!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LoisMay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值