cenylon 2020-05-15
多任务含义:
并发和并行
进程:正在运行的一个程序我们可以说是一个进程 ,是系统进行资源分配和调用的独立单元,每 一个进程都有自己独立的内存空间和系统资源
程序:运行起来的应用程序就称之为进程。也就是说当程序不运行的时候我们称之为程序, 当程序运行起来他就是一个进程。通俗的理解就是不运行的时候是程序,运行起来就是进程。 程序只有一个,但是进程可以有多个
线程:线程是进程中的一条执行线路或者流程,程序执行的最小单位,线程是任务调度的最小单 位。
进程和线程关系:一个进行可以有一个或者多个线程,但是一个线程只属于一个进程,一个进程中的多个线程是一种竞争关系
?
#一边下载歌曲,一边听歌 import time import threading def listen(): for i in range(1,6): print("正在听歌") time.sleep(1) def down_load(): for i in range(1,6): print("正在下载歌曲") time.sleep(1) # down_load() # sing() if __name__ == ‘__main__‘: #创建线程 t1 = threading.Thread(target=down_load) t2 = threading.Thread(target=listen) #执行线程 t1.start() t2.start() 执行顺序:首先程序运行时,程序从上往下走,遇到if __name__ == ‘__main__‘:,创建了两条线程,我们称之为子线程,程序运行时的线程我们称之为主线程 然后子线程根据target=xxx,开始执行指定的函数
线程注意点
线程何时开启,何时结束 1.子线程何时开启,何时运行 当调用start()时,开启线程,在运行线程的代码 2.子线程何时结束 子线程把target指向的函数中的语句执行完毕,当前子线程结束 3.查看线程数量 通过threading.enumerate()可以枚举当前运行的所有线程 4.主线程何时结束 所有子线程执行完毕后,主线程才结束
查看线程执行情况
import threading import time def test1(): for i in range(1,6): time.sleep(1) print("--子线程1--%d"%i) print("子线程1中查看线程情况:",threading.enumerate()) def test2(): for i in range(1,6): time.sleep(1) print("--子线程2--%d"%i) print("子线程2中查看线程情况:",threading.enumerate()) def main(): print("创建线程之前的线程情况:",threading.enumerate()) #创建线程 t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) time.sleep(1) print("创建线程之后的线程情况:", threading.enumerate()) #开启线程 t1.start() t2.start() time.sleep(1) print("调用start之后的线程情况:", threading.enumerate()) time.sleep(6) print("等待子线程执行结束后的线程情况:", threading.enumerate()) if __name__ == ‘__main__‘: main()
args传参
给函数传递参数,使用线程关键字args=( )进行传递参数,传递的参数是一个元组
import time import threading def listen(num): for i in range(1,num): print(f"正在听歌--{i}") time.sleep(1) def down_load(num): for i in range(1,num): print(f"正在下载歌曲--{i}") time.sleep(1) def main(): t1 = threading.Thread(target=down_load,args=(5,)) t2 = threading.Thread(target=listen,args=(3,)) t1.start() t2.start() if __name__ == ‘__main__‘: main()
join()方法
功能:当前线程执行完之后,其他线程才会继续执行
import time import threading def listen(num): for i in range(1,num): print(f"正在听歌--{i}") time.sleep(1) def down_load(num): for i in range(1,num): print(f"正在下载歌曲--{i}") time.sleep(1) def main(): #下载完歌曲,才可以听歌 t1 = threading.Thread(target=down_load,args=(5,)) t2 = threading.Thread(target=listen,args=(5,)) t1.start() t1.join() #在t1执行完之后,t2和主线程才执行 t2.start() t2.join() # 在t1,t2执行完后,再继续执行主线程 if __name__ == ‘__main__‘: main() print("程序执行结束了")
setDaemon()将当前线程设置成守护线程,来守护主线程
当主线程结束后,守护线程也就结束,不管是否执行完成,即主线程结束后不等待守护线程,立即结束
应用场景:qq多个聊天窗口,就可以设置为守护线程
#一边下载歌曲,一边听歌 import time import threading def listen(): for i in range(1,6): print("正在听歌") time.sleep(1) def down_load(): for i in range(1,6): print("正在下载歌曲") time.sleep(0.5) def main(): #创建线程 t1 = threading.Thread(target=down_load) t2 = threading.Thread(target=listen) # t1.setDaemon(True) t2.setDaemon(True) # 开启线程 t1.start() t2.start() if __name__ == ‘__main__‘: main() print("程序执行结束了")
threading模块提供的方法
threading.currentThread():返回当前的线程变量
threading.enumerate():返回一个包含所有正在运行的线程list。正在运行的线程:启动后,结束前,不包括启动前和终止后的线程
threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
线程.getName():获取线程名称
线程.setName():设置线程名称
线程.is_alive():判断线程存活状态
定义一个类继承threading.Thread
复写父类的run()方法
import threading import time class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = f"I‘m {self.name} @{i}" print(msg) def test1(): for i in range(5): #创建线程 t = MyThread() #开启线程 t.start() def main(): test1() main()
import threading import time g_num = 100 def work1(): global g_num for i in range(3): g_num+=1 print(f"--in work1,g_num is {g_num}") def work2(): global g_num print(f"--in work2,g_num is {g_num}") print("创建线程之前g_num:",g_num) t1 = threading.Thread(target=work1) t1.start() time.sleep(1) t2 = threading.Thread(target=work2) t2.start() def work1(g_nums): g_nums.append(44) print(f"--in work1,g_num is {g_nums}") def work2(g_nums): time.sleep(0.5) print(f"--in work2,g_num is {g_nums}") g_nums = [11,22,33] t1 = threading.Thread(target=work1,args=(g_nums,)) t1.start() t2 = threading.Thread(target=work2,args=(g_nums,)) t2.start()
多线程开发可能会遇到的问题
t1,t2两个线程都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num 加10次,那么g_num的最终结果应该是20 但是由于是多线程同时操作,可能会出现以下情况: 在g_num=0时,t1取的g_num=0,此时系统把t1调为“sleeping”状态,把t2转换为“running”状态,t2也获得g_num=0 然后t2对得到的值进行加1操作并赋值给g_num,使得g_num=1 然后系统又把把t2转换为“sleeping”状态,把t1调为“running”状态,线程t1把之前获取到0加1后赋值给g_num,使用g_num=1 这样导致虽然t1和t2都对g_num 加1,但结果仍是g_num=1 g_num = 0 def work1(num): global g_num for i in range(num): g_num+=1 #g_num=g_num+1 print(f"--in work1,g_num is {g_num}") def work2(num): global g_num #work1执行10次执行切换到work2 for i in range(num): g_num+=1 #g_num=g_num+1 print(f"--in work2,g_num is {g_num}") print("创建线程之前g_num:",g_num) t1 = threading.Thread(target=work1,args=(100,)) t1.start() t2 = threading.Thread(target=work2,args=(100,)) t2.start() #如果多个线程对同一个全局变量进行操作,会出现资源竞争,从而数据不准确,即会遇到线程安全问题
当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个 线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁
互斥锁为资源引入的一个状态:锁定/非锁定
使用过程:当某个线程要更改共享数据时,先将其锁定,此时资源的状态是“锁定”,其他线程不能访问,直到该线程释放资源,将资源改为“非锁定”状态,其他线程才可以再次锁定该资源。
import threading #创建锁 mutex = threading.Lock() #锁定 mutex.acquire() #释放 mutex.release() 注意:如果这个锁之前没有上锁,那么acquire不会堵塞 如果在调用acquire 对这个锁上锁之前,他已经被其他线程上了锁,此时acquire会堵塞,直到锁被解锁位置
#使用互斥锁完成2个线程对同一全局变量各加100万次操作 g_num = 0 def work1(num): global g_num for i in range(num): # 锁定 mutex.acquire() g_num+=1 # 释放 mutex.release() def work2(num): global g_num #work1执行10次执行切换到work2 for i in range(num): # 锁定 mutex.acquire() g_num += 1 # 释放 mutex.release() #创建锁 mutex = threading.Lock() t1 = threading.Thread(target=work1,args=(1000000,)) t1.start() t2 = threading.Thread(target=work2,args=(1000000,)) t2.start() while len(threading.enumerate())!=1: time.sleep(1) print(f"--最终结果,g_num is {g_num}")
在多个线程共享资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源, 就会造成死锁现象,尽管死锁很少发生,但是一旦发生就会造成应用程序停止相应
import time def test1(): #lock1上锁 lock1.acquire() print("--test1开始执行--") time.sleep(1) # lock2上锁 lock2.acquire() print("--test1执行完成--") lock2.release() #lock2解锁 lock1.release() #lock1解锁 def test2(): # lock2上锁 lock2.acquire() print("--test2开始执行--") time.sleep(1) # lock1上锁 lock1.acquire() print("--test2执行完成--") lock1.release() # lock1解锁 lock2.release() # lock2解锁 lock1 = threading.Lock() lock2 = threading.Lock() if __name__ == ‘__main__‘: t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() t2.start()
总结 :在多线程开发中,全局变量是多个线程都共享的数据,而局部变量等是各自线程的,是非共 享的
队列:Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先入先出)队列 Queue, LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。这些队列都实现了锁原语 (可以理解为原子操作,即要么不做,要么就做完),能够在多线程中直接使用。可以使用 队列来实现线程间的同步
from queue import Queue #1.创建队列对象 # q = Queue(maxsize=3) #3表示最多存放3个数据 #参数maxsize 是队列中允许的最大项,如果省略次参数,则无大小限制, #返回值q是队列对象 # 2.put()方法 , 项队列中存放数据 如果q为空,此方法阻塞,直到队列可用为止 # 3.get()方法 ,返回队列中的一个项目,如果队列为空,此方法阻塞,直到队列可用为止 # 4.get_nowait(), 不等待,直接抛出异常 # 5.full() , 如果q已满,返回True # 6.empty(), 如果q已空,返回True # 7.qsize(), q中数据的个数 q = Queue(maxsize=3) q.put("张伟强") # print(q.empty()) q.put("刘江") q.put("张威") print(q.full()) # q.put("徐伟明") print(q.get()) print(q.get()) print(q.get()) # print(q.get(timeout=3)) # print(q.get_nowait()) print(q.qsize())
生产者与消费者模式
生产者:生产数据的线程
消费者:处理数据的线程
目的:平衡生产者和消费者的工作能力来提高程序的整体处理数据的速度
import threading import queue import time def producer(name): #生产者 count = 1 while True: print("%s生产了包子%d"%(name,count)) q.put(count) count+=1 time.sleep(0.2) print("包子总数",q.qsize()) def costom(name): #消费者 while True: print("%s吃了包子%d"%(name,q.get())) time.sleep(1) if __name__ == ‘__main__‘: q = queue.Queue(maxsize=3) t1 = threading.Thread(target=producer,args=("张大厨",)) t2 = threading.Thread(target=costom, args=("吃货刘",)) t3 = threading.Thread(target=costom, args=("吃货朱",)) t1.start() t2.start() t3.start()
为什么使用生产者和消费者模式
什么是生产者消费者模式