从0基础学习Python[多线程及同步]
多任务回顾
什么是任务
一个程序的每一个模块都视为一个任务,一个程序至少是一个任务,计算机执行程序或代码都是按照任务去分配的。
可以理解为每一个程序的运行对于计算机而言都是他的任务工作。目前的系统都是多任务系统,每个系统可以同时运行多个程序
多任务的实现
从程序的运行层次而言,多任务分为:并发和并行
并发
同时运行的程序任务他们在同一个运行时间线上交替快速的轮流执行,会产生一个假的同时运行效果
消息高并发?
并行
以多进程的方式更高效率的使用计算机的所有内核去完成,将程序的进程分配给更多的内核去单独处理每个任务中的每个进程。
只有多核CPU才能实现真正的多任务处理
从编程层次而言,多任务的实现可以通过:多进程、多线程、协程
线程
什么是线程
python中的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装,可以更加方便的被使用
线程就是车间中的工人,实际干活的也是工人,真正执行程序的还是线程
单线程
程序中的代码按照计算机的线程去逐行执行
线程就是车间中的工人,实际干活的也是工人,真正执行程序的还是线程
import time
# print("{:.2%}".format(0.3))
def upload():
print("开始上传。。。")
for i in range(1,5):
print(f"上传了{'{:.2%}'.format(i / 4)}")
time.sleep(1)
print("上传完毕。。。")
def download():
print("开始下载.......")
for i in range(1,5):
print(f"下载了{'{:.2%}'.format(i / 4)}")
time.sleep(1)
print("下载完毕.......")
upload()
download()
多线程
import time
import threading
def upload():
print("开始上传.......")
for i in range(1, 5):
print(f"上传了{'{:.2%}'.format(i / 4)}")
time.sleep(1)
print("上传结束.......")
def download():
print("开始下载.......")
for i in range(1, 5):
print(f"下载了{'{:.2%}'.format(i / 4)}")
time.sleep(1)
print("下载完毕.......")
def main():
upload_thread=threading.Thread(target=upload)
down_thread=threading.Thread(target=download)
upload_thread.start()
down_thread.start()
if __name__ == '__main__':
main()
查看线程数量
明白一个问题,一个程序的执行必然有一个线程,这个线程是主线程
也明白一个问题,一个程序的执行必然有一个进程,这个进程是主进程
threading.enumerate()
线程的特点
-
线程执行代码的封装
通过使用threading模块能够完成多任务的程序开发,为了让每个线程的封装性更完美所以使用threading模块时,往往会自定义一个新的子类class,只要继承threading.Thread即可实现
import time import threading class MyThread(threading.Thread): # 重写Thread类里面的run方法 def run(self): print("开始上传.......") for i in range(1, 5): print(f"上传了{'{:.2%}'.format(i / 4)}") time.sleep(1) print("上传结束.......") self.download() def download(self): print("开始下载.......") for i in range(1, 5): print(f"下载了{'{:.2%}'.format(i / 4)}") time.sleep(1) print("下载完毕.......") if __name__ == '__main__': t = MyThread() t.start()
一般情况下一个线程中的入口函数是run
-
多线程共享全局变量
在一个函数中对全局变量进行修改的时候,到底是否需要看是否对全局变量的执行进行了引用修改,如果修改了引用,也就是说让全局变量指向了一个新的地方,如果仅修改了引用的数据,此时不用担心变量被分化
import time # 1.声明一个全局变量 num = 0 def work1(): global num for i in range(30000000): num += i print(f"在work1最终的数据是:{num}") def work2(): global num print(f"在work2最终的数据是:{num}") if __name__ == '__main__': t1=threading.Thread(target=w t2=threading.Thread(target=w t1.start() # time.sleep(1) t2.start() print(f"最终的数据是:{num}")
共享全局变量的问题
假设两个线程1,2都要对全局变量进行修改,如果数据量小基本上没有任何影响,皆大欢喜。但是如果数据量大了,会发现新的问题:数学白学了
import threading
num = 0
def add1():
global num
for _ in range(10000000):
num += 1
print(f"add1运算的最终结果是num={num}")
def add2():
global num
for _ in range(10000000):
num += 1
print(f"add2运算的最终结果是num={num}")
if __name__ == '__main__':
t1=threading.Thread(target=add1)
t2=threading.Thread(target=add2)
t1.start()
t2.start()
print(f"最终结果是num={num}")
同步
什么是同步
同步指的是协同步调,按预定的先后次序进行运行
线程通过或者进程同步,可以列几位进程或线程A和B一块配合,A执行到一定程度需要依靠B的某个结果,如果B还没有执行完得出结果,那么A就需要停下来等到B运行,反之也是如此
解决线程同时修改全局变量的方式
如何实现呢?
互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程的同步能够保证多个线程安全访问竞争资源,最简单的同步机制就是引入互斥锁
互斥锁为资源引入了一个状态:锁定/非锁定
某个线程需要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能修改,直到该线程释放资源,将资源的状态变成“非锁定”,其他线程才能够再次锁定该资源,互斥锁保证了每次只有一个线程进行写入工作,从而保证了多线程情况下的数据正确
threading模块中定义了Lock锁,可以方便的处理锁定
1.先创建互斥锁
mutex=threading.Lock()
2.锁定资源
mutex.acquire()
3.解锁资源
mutex.release()
在使用互斥锁的同时,锁定的资源越少越好
死锁
在线程间共享多个资源的时候,如果每个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁
如何避免死锁:
- 程序设计时要尽量避免(银行家算法)
- 添加超时时间
Day28-------END