GIL互斥锁与线程
GIL互斥锁验证是否存在
"""
昨天我们买票的程序发现很多个线程可能会取到同一个值进行剪除,证明了数据是并发的,但是我们为了证明在Cpython中证明是存在GIL那么我们就使用列表将他存起来,证明有GIL是串连而不是并发态
"""
from threading import Thread
count = 100
def task():
global count
count -= 1
t1_list = []
if __name__ == '__main__':
for i in range(100):
t = Thread(target=task)
t.start()
t1_list.append(t)
for t in t1_list:
t.join()
print(t1_list)
print(count) # 0
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811205307064-411731727.png)
GIL互斥锁的特点
"""
我们在给它加一个IO操作的时候那么他就不会产生GIL,所以它不会影响程序层面的数据也不会保证他的修改是安全的想要保证就需要程序员手动给她加一把锁
"""
from threading import Thread
import time
count = 100
def task():
global count
num = count
time.sleep(0.1)
count = num - 1
t1_list = []
if __name__ == '__main__':
for i in range(100):
t = Thread(target=task)
t.start()
t1_list.append(t)
for t in t1_list:
t.join()
print(t1_list)
print(count) # 99
"""
自己给自己加锁来完成统计
"""
from threading import Thread,Lock
import time
count = 100
mutex = Lock()
def task():
mutex.acquire()
global count
num = count
time.sleep(0.1)
count = num - 1
mutex.release()
t1_list = []
if __name__ == '__main__':
for i in range(100):
t = Thread(target=task)
t.start()
t1_list.append(t)
for t in t1_list:
t.join()
print(t1_list)
print(count) # 0
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811205515334-1259146495.png)
验证python多线程的作用
1.io密集型的多进程
需要在内存中多次申请额外的内存空间,需要消耗更多的时间和资源
2.io密集型的多线程
只需要在进程中申请自己所需要的代码即可,只需要通过多道的技术那么就可以实现多线程操作,消耗的资源和时间较少
3.计算密集型的多进程
同样需要在内存中申请额外的内存空间,然后消耗更多的时间和资源(总计算耗时和申请内存空间的时间,拷贝代码的时间再加上各个进程之间切换的时间)
4.计算密集型的多线程
只需要在进程中申请自己所需要的代码即可,并且通过多道技术直接执行(总计算消耗的时间和切换到各线程的时间)
5.总结
所以说在单cup中python的多线程还是非常有用的,即节省内存空间又速度较快
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811205408300-1673385570.png)
1.io密集型的多进程
总耗时(单个进程的耗时+IO操作的耗时+申请内存空间的耗时+拷贝代码的耗时)
2.io密集型的多线程
总耗时(单个线程的耗时+IO操作的耗时)
3.计算密集型的多进程
总耗时(单个进程的耗时)
4.计算密集型的多线程
总耗时(多个进程的耗时总和,但是由于内部有优化代码,所以实际上没有那么长的时间进行计算)
5.总结
在多CUP进行密集型计算时由于多进程可以有多个核进行计算所以计算时间会非常短所以在多CPU多计算模型的情况下多进程占据主要又是,也可以称之为完胜,而多线程因为没有办法享用多cpu的好处所以只能一个CPU慢慢计算。
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811205715962-1834805067.png)
1.小知识使用代码查看自己电脑是几核处理
import os
print(os.cpu_count()) # 12
2.展示多计算状态的运行
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):
p = Process(target=work)
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('总耗时:%s'%(time.time() - start_time)) # 总耗时:5.136746883392334
if __name__ == '__main__':
start_time = time.time()
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)) # 总耗时:27.33429765701294
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811210015616-1667430030.png)
死锁现象
"""
死锁的情况是因为,想要获取我们的想要获取的对象在对方手中而对象想要获取的锁在我们的手中那么就会出现死锁的情况,两方都想要对方的锁,但是自身都有锁
"""
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锁')
time.sleep(3)
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(3)
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 = Thread()
t.start()
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811210056613-1007358728.png)
信号量
- 信号量其实本质上也是互斥锁,只不过他这个是多把锁我们可以定义他这个锁的个数,控制线程的最大运行量
- 信号量在不同的变成语言体系中代表的意思可能不太一样,在我们python的并发编程中代表的是多把互斥锁的意思,而在djangomouge中,他则是代表的是某个条件出发的中间件所以不能一概而论,听到信号量就下定论
- 信号量演示
from threading import Thread,Semaphore
import time
import random
SP = Semaphore(3) # 设置信号量为3也就是三把锁
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()
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811210220548-463049136.png)
event事件
- 子进程之间可以互相彼此等彼此使用event就可以让一个进程等另一个进程发号师令然后开始运行
- 展示
from threading import Thread,Event
import time
import random
event = Event()
def light():
print('比赛准备开始,本场次有黑哨请所有人启动赛车听到自己的指令出发')
time.sleep(random.randint(1,5))
print('预备,鸣发动机,let GO!GO!GO!')
event.set() # 发送指令
def car(name):
print(f'{name}正在启动赛车')
event.wait() # 等待指令
print(f'{name}发动机轰鸣后出发了')
t = Thread(target=light)
t.start()
l1 = ['Joseph','Alice','Trump','kdi','jason']
for i in l1:
t = Thread(target=car,args=(f'选手{i}',))
t.start()
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811210443223-778424727.png)
进程池与线程池和概念
- 在我们的进程和线程中既然我门做服务的时候可以产生很多的进程和线程同时运行的结果,那么我们可以无限制的开启进程和线程可以嘛
- 哒咩,肯定不行鉴于我们的科技水平和技术发展程度,都没有任何硬件可以支持我们进行无限制开启进程和线程,所以我们就需要在硬件的承受范围内创造一个阀值以防资源消耗过度使之损毁。那么产生了我们的进程池和线程池的作用
- 进程池和线程池都会帮我们在使用之前就创建好空的进程和线程,我们以后用的时候就会自动分配给你,不需要重新创建。
进程池与线程池的实际操作
"""
进程池和线程池本质上就是帮它定了一个阀值,让他只能在这个阀值内运行多了那么对不起,你等着吧
"""
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import os
import time
pool1 = ProcessPoolExecutor(3)
pool2 = ThreadPoolExecutor(3)
def task(n):
print(current_thread().name)
print(os.getpid())
print(os.getppid())
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(5):
res = pool1.submit(task,123)
print(res.result())
pool1.submit(task,123).add_done_callback(func)
if __name__ == '__main__':
for i in range(5):
res = pool2.submit(task,123)
print(res.result())
pool2.submit(task,123).add_done_callback(func)
pool2.submit(task,123).add_done_callback(func)
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811210615676-1458090066.png)
协程简介
- 进程:资源单位,需要从内存中开辟空间
- 线程:执行单位,只需要在进程中执行
- 协程:单线程下实现并发(携程:一款旅行软件,住酒店看携程)
- 协程其实就是在单线程下我们的线程进入IO状态的情况下欺骗CPU让它觉得没有遇到io操作继续运行代码,其实io被我们的代码检测出来一旦有的话就立即让cpu执行别的东西实现无缝连接,这个东西是程序员自己搞出来的,名字也是它起的(协调程序运行,黑心老板压榨cpu剩余劳动力)
from gevent import monkey;monkey.patch_all() # 就是这样写的,我也不知道为什么(猴子补丁)
from gevent import spawn
import time
def func1():
print('func1 正在执行')
time.sleep(3)
print('func1 执行完毕')
def func2():
print('func2 正在执行')
time.sleep(6)
print('func2 执行完毕')
if __name__ == '__main__':
start_time = time.time()
# func1()
# func2() # 9.022636413574219
s1 = spawn(func1) # 一旦遇到io操作那么就立即跳到别的地方执行,如果都在io那么久反复横跳
s2 = spawn(func2) # # 6.01244044303894
s1.join()
s2.join()
print(time.time() - start_time)
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811210803133-2079435698.png)
协程的实际作用简单实现单线程协程工作
1.服务端
import socket
from gevent import monkey;monkey.patch_all()
from gevent import spawn
def communication(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf-8'))
sock.send(data.upper())
def get_server():
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
sock,address = server.accept()
spawn(communication,sock)
s1 = spawn(get_server)
s1.join()
2.客户端
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'good night {current_thread().name}'.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
for i in range(20):
t = Thread(target=get_client)
t.start()
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811210710592-1085758703.png)
论如何累死一头牛
- 在多进程的情况下使用多线程操作并且在多线程的情况下使用多协程操作,资本家看了都泪目的行为,纯纯的极致压迫和剥削。
![image](https://img2022.cnblogs.com/blog/2904194/202208/2904194-20220811210943399-1339271826.png)