并发编程(二)
并发编程(二)
1.GIL与普通互斥锁的区别
# 1.验证GIL的存在
from threading import Thread,Lock
import time
s = 100
def task():
global s
s -= 1
for i in range(100): # 创建100个线程
t = Thread(target=task)
t.start()
print(s) # 0
# 2.验证不同数据加不同锁
from threading import Thread,Lock
import time
s = 100
mutex = Lock()
def task():
global s
mutex.acquire()
num = s
time.sleep(0.1)
s = num - 1
mutex.release()
t_list = []
for i in range(100): # 创建100个线程
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(s) # 0
'''
抢锁放锁也有简便写法,就是with上下文管理
with mutex:
pass
'''
# 3.GIL是一个纯理论知识,它的作用面很窄,仅限于解释器级别,所以我们想要保证数据的安全应该自定义互斥锁,使用别人封装
好的工具
2.验证多线程作用
# 1.两个前提
1.CPU的个数(单个、多个)
2.任务的类型(IO密集型、计算密集型)
# 2.单个CPU
1.多个IO密集型任务
多进程:浪费资源,无法利用多个CPU
多线程:节省资源,切换加保存状态
2.多个计算密集型任务
多进程:耗时更长,创建进程的消耗加上切换的时间
多线程:耗时较短,切换会消耗时间
# 3.多个CPU
1.多个IO密集型任务
多进程:浪费资源,多个CPU没有用处
多线程:节省资源,切换加保存状态
2.多个计算密集型任务
多进程:利用多核,速度更快
多线程:速度较慢
'''多进程与多线程都有具体的应用场景'''
# 4.代码演示
# 计算密集型
from threading import Thread
from multiprocessing import Process
import os
import time
def work():
res = 1
for i in range(1,10000):
res *= i
if __name__ == '__main__':
# print(os.cpu_count()) # 8 查看当前计算机CPU个数
start_time = time.time()
# p_list = []
# for i in range(8):
# p = Process(target=work)
# p.start()
# p_list.append(p)
# for p in p_list:
# p.join()
# print('总耗时:%s'%(time.time() - start_time))
t_list = []
for i in range(8):
t = Thread(target=work)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print('总耗时:%s' % (time.time() - start_time))
'''
计算密集型
多进程:总耗时:0.2672848701477051
多线程:总耗时:0.19750189781188965
'''
# 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.012962579727172852
多线程:总耗时:0.4617276191711426
'''
3.死锁现象
from threading import Thread,Lock
import time
mutex1 = Lock()
mutex2 = Lock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
mutex1.acquire()
print(f'{self.name}抢到了第一个锁')
mutex2.acquire()
print(f'{self.name}抢到了第二个锁')
mutex2.release()
mutex1.release()
def f2(self):
mutex2.acquire()
print(f'{self.name}抢到了第二个锁')
time.sleep(2)
mutex1.acquire()
print(f'{self.name}抢到了第一个锁')
mutex1.release()
mutex2.release()
for i in range(20):
t = MyThread()
t.start()
'''
Thread-1抢到了第一个锁
Thread-1抢到了第二个锁
Thread-1抢到了第二个锁
Thread-2抢到了第一个锁
'''
4.信号量
# 信号量
信号量在不同的知识体系中,展现出来的功能是不一样的,在并发编程中信号量的意思是多把互斥锁,在Django框架中信
号量的意思是达到某个条件自动触发特定功能。
如果将自定义互斥锁比喻成是单个座位,信号量就相当于班级,有很多的座位。
# 代码演示
from threading import Thread,Semaphore
import time
import random
sp = Semaphore(5) # 创建一个有五个座位的班级
def task(name):
sp.acquire()
print('%s先来坐下'% name)
time.sleep(random.randint(1,5))
sp.release()
for i in range(1,30):
t = Thread(target=task,args=('oscar%s'%i,))
t.start()
5.event事件
# event事件
event事件就是子线程的运行可以由其他子线程决定
# 代码演示
from threading import Thread,Event
import time
event = Event() # 类似于建了一个红绿灯
def light():
print('红灯停')
time.sleep(3)
print('绿灯行')
event.set()
def car(name):
print(f'{name}正在等红灯')
event.wait()
print(f'{name}开车走了')
if __name__ == '__main__':
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car,args=('oscar%s'%i,))
t.start()
6.进程池与线程池
# 服务端三要素
1.24小时不间断提供服务
2.固定的IP和port
3.支持高并发
# TCP服务端实现并发
多进程:来一个客户端就打开一个进程
多线程:来一个客户端就打开一个线程
# 进程池与线程池
但是呢计算机硬件是有物理极限的,我们不可能无限制的创建进程和线程,所以就可以用池来保证计算机硬件安全的情况
下提升程序的效率。
进程池:提前创建好固定数量的进程,后续反复使用这些进程
线程池:提前创建好固定数量的线程,后续反复使用这些线程
如果任务超出了池子里面的最大进程或线程数,就原地等待,虽然进程池和线程池降低了程序的运行效率,但是保证了硬件的
安全。
# 代码演示
'''线程池'''
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
# 这句代码执行之后就会立刻创建五个等待工作的线程
pool = ThreadPoolExecutor(5) # 线程池线程数默认是CPU个数的五倍,也可以自定义
def task(n):
time.sleep(2)
print(n)
return '任务的执行结果:%s'%n**2
def func(*args,**kwargs):
print(args[0].result)
for i in range(20):
pool.submit(task,i).add_done_callback(func) # add_done_callback只要任务有结果了,就会自动调用括号内的函
数处理
'''进程池'''
pool = ProcessPoolExecutor(5) # 进程池进程数默认是CPU个数,也可以自定义
pool.submit(task,i).add_done_callback(func)
7.协程
# 协程
单线程下实现并发就是协程,协程不是官方名词,是程序员自定义的名称,对于操作系统而言只认识进程和线程,并不认
识协程,协程是自己通过代码来检测程序的IO操作并自己处理,让CPU察觉不到IO操作的存在,从而最大化的利用CPU。
# 代码演示
from gevent import monkey;monkey.patch_all() # 固定编写,用于检查所有的IO操作
from gevent import spawn
import time
def play(name):
print(f'{name}来了')
time.sleep(5)
print(f'{name}又来了')
def eat(name):
print(f'{name}走了')
time.sleep(2)
print(f'{name}走远了')
start_time = time.time()
g1 = spawn(play, 'oscar')
g2 = spawn(eat, 'oscar')
g1.join()
g2.join()
print('总耗时',time.time() - start_time) # 总耗时 5.033620357513428
这里是IT小白陆禄绯,欢迎各位大佬的指点!!!
