day030学习笔记
线程同步
-
概念
- 多个线程⼏乎同时修改某⼀个共享数据的时候, 需要进⾏同步控制
- 引入互斥锁以确保每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正 确性(原子性)
- 互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资 源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只 有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
-
效果
- 确保对数据的修改正确
- 会降低执行效率
-
互斥锁实现
- 创建锁
mutex = threading.Lock() - 锁定
mutex.acquire() - 释放
mutex.release() - 理解:线程可以从锁中切出,但是其他线程的锁中内容必须等当前锁完全执行完才能继续获取锁(被阻塞)
import threading import time num = 0 def f1(): global num mutex.acquire() print("进入锁",num) time.sleep(2) for i in range(1000000): num += 1 print("f1操作后",num) mutex.release() def f2(): global num print("f2中",num) mutex.acquire() #如果后被调度到此会被阻塞 for i in range(1000000): num += 1 print("f2操作后",num) mutex.release() num += 88 mutex = threading.Lock() t1 = threading.Thread(target=f1) t2 = threading.Thread(target=f2) t1.start() t2.start() time.sleep(3) print(num) >>>进入锁 0 #f1先获得锁 >>>f2中 0 #切到f2中的锁之前 >>>f1操作后 1000000 >>>f2操作后 2000000 #等待f1锁中内容执行完后(当前锁的阻塞状态解除),获取锁开始执行 >>>2000088 - 创建锁
-
死锁
多个线程多个锁互相需要但多个线程抢到了不同的锁(互相等待对方释放锁)
-
信号量Semaphore
- 控制一个时间点内线程进入数量的锁,信号量用来控制线程并发数
- 理解为计数器,仅起到控制线程个数的功能
import threading import time s1 = threading.Semaphore(2) def f1(): s1.acquire() print("123") time.sleep(1) s1.release() for i in range(10): t = threading.Thread(target=f1) t.start()
GIL全局解释器锁
- 执行Python文件的过程
- 操作系统将你的应用程序从硬盘加载到内存。运行python文件,在内存中开辟一个进程空间,将你的 Python解释器以及py文件加载进去,解释器运行py文件。
- Python解释器分为两部分,先将你的代码通过编译器编译成C的字节码,然后你的虚拟机拿到你的C的字节码,输出机器码,再配合操作系统把你的这个机器码扔给cpu去执行
- py文件中有一个主线程,主线程做的就是这个过程。如果开多线程,每个线程都要进行这个过程
- GIL全局解释器锁做了什么
- Cpython在所有线程进入解释器之前加了一个全局解释器锁(GIL锁)-互斥锁
- 导致同一时间只有一个线程在执行所以无法使用多核(即不能同时多个线程一起执行)
同步和异步
-
概念
- 同步调⽤:确定调用的顺序
- 提交一个任务,自任务开始运行直到此任务结束,我再提交下一个任务
- 顺序、统一的时间轴
- 异步调⽤:不确定顺序
- 一次提交多个任务,然后我就直接执行下一行代码
- 乱序、效率优先的时间轴
- 同步调⽤:确定调用的顺序
-
线程同步
多个线程有序执⾏(通过互斥锁实现)-有序释放锁;
import threading,time class Task1(threading.Thread): def run(self) -> None: while True: lock1.acquire() print("task1") time.sleep(1) lock2.release() class Task2(threading.Thread): def run(self) -> None: while True: lock2.acquire() print("task2") time.sleep(1) lock3.release() class Task3(threading.Thread): def run(self) -> None: while True: lock3.acquire() print("task3") time.sleep(1) lock1.release() lock1 = threading.Lock() lock2 = threading.Lock() lock2.acquire() #先将两个进程锁住(提前获取锁) lock3 = threading.Lock() lock3.acquire() t1 = Task1() t2 = Task2() t3 = Task3() t1.start() t2.start() t3.start() >>>task1 >>>task2 >>>task3 …… -
⽣产者消费者模式
将一块缓冲区放在生产者和消费者之间,两者的数据交互不互相依赖(降低了耦合性)
-
实现方式-消息队列
动态设置放与取的区间,防止发生阻塞
import queue import threading def write(): while True: if q1.qsize() <= 600: for i in range(1,51): q1.put(f"新数据{i}") def read(): while True: if q1.qsize() >= 100: for i in range(20): print("读取",q1.get()) q1 = queue.Queue() for i in range(1,500): q1.put(f"初始数据{i}") t1 = threading.Thread(target=write) t2 = threading.Thread(target=read) t1.start() t2.start()
-
-
异步实现
-
同时启动多个线程(批量创建,批量就绪)
-
进程的回调
from multiprocessing import Pool,Process import random def download(name): import time for i in range(1,4): print(f"{name}下载文件{i}") time.sleep(random.randint(1,3)) return f"{name}下载完成" def alterUser(msg): print(msg) if __name__ == "__main__": p = Pool(3) # 当func执行完毕后,return的东西会给到回调函数callback p.apply_async(func=download,args=("进程1",),callback = alterUser) p.apply_async(func=download,args=("进程2",),callback = alterUser) p.apply_async(func=download,args=("进程3",),callback = alterUser) p.close() p.join()
-
协程
-
概念
- ⽐线程更⼩的执⾏单元(微线程)
- ⼀个线程作为⼀个容器⾥⾯可以放置多个协程(理解为协助实现多线程)
- 只切换函数调用即可完成多线程,可以减少CPU的切换
- 协程⾃⼰主动让出CPU
-
协程实现
-
greenlet 模块(需要安装)
- 特性:手动设置切换位置
from greenlet import greenlet def t1(): while True: # sum = 0 print(sum([i for i in range(1,101)])) gr2.switch() a = input("输入1:") print(a) def t2(): while True: # sum = 0 print(sum([i for i in range(1,101)])) gr1.switch() a = input("输入2:") print(a) gr1 = greenlet(t1) gr2 = greenlet(t2) gr1.switch() -
gevent 模块
- 特性:⾃动切换任务(操作系统控制)
- 遇到IO(指的是input output 输⼊输出)操作时(模块能识别的), ⽐如访问⽹络, 就⾃动切换到其他的greenlet, 等到IO操作完成, 再在适当的时候切换回来继续执⾏
- 如果所有程序都没有IO操作,那么就基本属于串行执行了
import gevent def t1(): while True: # sum = 0 print(sum([i for i in range(1,101)])) gevent.sleep(1)#用来模拟一个耗时操作 #gevent中:当一个协程遇到耗时操作会自动交出控制权给其他协程 a = input("输入1:") print(a) def t2(): while True: # sum = 0 print(sum([i for i in range(1,101)])) gevent.sleep(1) a = input("输入2:") #每当遇到耗时操作,会自用转到其他协程 print(a) gr1 = gevent.spawn(t1) # 创建一个gevent对象(创建了一个协程),此时就已经开始执行t1 gr2 = gevent.spawn(t2) gr1.switch()
-
浙公网安备 33010602011771号