一、进程
1.多个进程中的全局变量
#进程的特点:独立性,并发性,动态性 #多个进程间的全局变量:独立性【进程之间是相互独立的,资源不共享】 from multiprocessing import Process from time import sleep #全局变量 num = 100 def run(): print("子进程开始") global num num += 1 print("子进程中的num=%d" % (num)) print("子进程结束") if __name__ == "__main__": print("父进程开始") p = Process(target=run) p.start() #等待子进程结束之后父进程才结束,进程在执行的过程中是由系统分配的,cpu调度 p.join() sleep(3) """ 说明:在子进程中修改全局变量对父进程中的全局变量没有影响 工作原理:在创建子进程对象的时候,实际上已经对全局变量做了一个备份, 父进程中的num与子进程中是完全不同的两个变量 结论:进程是系统中程序执行和资源分配的基本单位,每个进程都有自己的数据段,代码段和堆栈段【进程之间资源不共享】 """ print("父进程中的num=%d" % (num)) print("父进程结束")
2.启动大量子进程
""" 启动多个进程: a.创建多个Process类 b.如果要启动大量的子进程,可以用进程池的方式批量创建子进程 """ from multiprocessing import Pool import time,os,random def run(name): print("子进程-%s-启动:%s" % (name,os.getpid())) start = time.time() time.sleep(random.randint(1,3)) end = time.time() print("子进程-%s-结束:%s,耗时:%.2f" % (name, os.getpid(),end - start)) if __name__ == "__main__": print("父进程启动") #1.创建进程池的对象 """ 语法:Pool(num),num表示可以同时执行的进程数量,num默认为当前cpu核心数量 """ p = Pool(4) #2.通过循环的方式创建多个子进程,添加到进程池中进行统一的管理 for i in range(10): #多个子进程处理同一件任务 p.apply_async(run,args=(i,)) #3. """ a.进程池对象调用join,会等待进程池中的所有的子进程结束完毕,父进程才结束 但是,在进程池中,必须调用join b.在调用join之前必须先调用close,调用close之后就不能再添加新的进程对象 """ p.close() p.join() print("父进程结束")
3.进程之间的通信
""" Process之间是需要通信的,进程之间本来是相互独立的,但是,可以借助于Queue【队列】完成数据交换 """ from multiprocessing import Process,Queue import os,time list1 = ["A","B","C"] #1.写数据的任务 #q是队列 def write(q): print("进程%s开始,开始写数据" % (os.getpid())) for value in list1: print("add %s to queue....." % (value)) #向队列中添加数据 q.put(value) time.sleep(1) print("进程%s结束" % (os.getpid())) #2.读数据的任务 def read(q): print("进程%s开始,开始读数据~~~~~" % (os.getpid())) for i in range(len(list1)): #从队列中获取数据 value = q.get(True) print("get %s from queue....." % (value)) print("进程%s结束~~~~~" % (os.getpid())) if __name__ == "__main__": print("父进程开始") #创建队列对象 q = Queue() #创建子进程的对象 pw = Process(target=write,args=(q,)) pr = Process(target=read, args=(q,)) pr.start() pw.start() #time.sleep(7) pr.join() pw.join() print("父进程结束")
二、线程
多任务可以由多进程完成,也可以由一个进程内的多线程完成
在一个进程的内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”叫做线程
线程通常叫做轻型的进程。线程是共享内存空间的并发执行的多任务,每一个线程都共享一个进程中的资源
线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,也不能决定执行多长时间
而且线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程
1.创建并启动线程
""" process:进程 thread:线程 _thread模块:提供了低级别的,原始的线程【只是功能比较有限】 threading模块:高级模块,对_thread模块进行封装,并提供了_thread模块没有的功能 通过threading模块中Thread类创建线程对象,和进程类似,线程对象同样需要调用start """ import threading,time #3.子线程的任务 def run(): print("子线程启动:%s" % (threading.current_thread().name)) time.sleep(2) print("子线程结束:%s" % (threading.current_thread().name)) def run1(num): print("子线程启动:%s" % (threading.current_thread().name)) print(num) time.sleep(2) print("子线程结束:%s" % (threading.current_thread().name)) if __name__ == "__main__": #1.程序一旦启动,就会启动一个进程【父进程/主进程】,一个进程至少启动一个线程, # 默认启动的线程被称为主线程 #threading.current_thread()获取当前正在执行的线程对象,该对象的name属性表示线程的名称 #主线程的名称默认为MainThread print("主线程启动:%s" % (threading.current_thread().name)) #2.在主线程中创建子线程 """ Thread(target,name,args) """ #2.1不指明名称,使用默认名称,子线程默认的名称为:Thread-1,Thread-2.... # t1 = threading.Thread(target=run) # t1.start() #2.2自定义线程名称 # t2 = threading.Thread(target=run,name="子线程-果冻") # t2.start() #2.3任务函数有参数 t3 = threading.Thread(target=run1, name="子线程-土豆",args=(45,)) t3.start() # time.sleep(4) # 4.和进程类似,子线程也可以完成合并 t3.join() print("主线程结束:%s" % (threading.current_thread().name))
2.线程中的共享数据
#进程:资源不共享, 线程:资源共享 """ 在多线程中,全局变量由所有线程共享,所以,任何一个全局变量可以被任意一个线程修改, 线程之间共享数据最大的危险在于:容易将数据改乱 """ import threading,time #假设这是你的银行存款 balance = 0 def change(n): #先存后取,理论上上应该为0 global balance balance = balance + n balance = balance - n def run(m): for i in range(1000000): change(m) if __name__ == "__main__": t1 = threading.Thread(target=run,args=(5,)) t2 = threading.Thread(target=run, args=(8,)) t1.start() t2.start() t1.join() t2.join() print("balance=%d" % (balance)) """ 问题: 理论上结果为0,但是,线程的调度由操作系统决定的 当t1,t2交替执行时,只要循环次数够多,balance的值就不一定为0,有可能是正数,也有可能是负数 问题的本质:使用多线程解决问题出现的临界资源问题 原因分析: 因为高级语言的一条语句在cpu执行的时候是若干条语句 举例:balance = balance + n分为两步执行: a.计算balance + n,存入到临时变量中 ,x = balance + n b.将临时变量的值给balance赋值,balance = x 而两个线程各自有自己的x 1.正常 t1:x1 = balance + 5 t1:balance = x1 t1:x1 = balance - 5 t1:balance = x1 t2:x2 = balance + 8 t2:balance = x2 t2:x2 = balance - 8 t2:balance = x2 balance = 0 #2. t1:x1 = balance + 5 #x1 = 5 t2:x2 = balance + 8 #x2 = 8 t2:balance = x2 #balance = 8 t1:balance = x1 #balance = 5 t1:x1 = balance - 5 #x1 = 0 t1:balance = x1 #balance = 0 t2:x2 = balance - 8 #x2 = -8 t2 = balance = x2 #balance = -8 结果:balance = -8 因为修改balance需要多条语句,当执行语句时,线程可能会中断 , 从而导致多个线程把同一个对象的内容改乱了 解决方案: 可以给临界资源上一把锁 给临界资源上锁,当一个线程访问的时候, 其他的线程就需要在锁外面等待,知道锁被释放之后,获得该锁的线程才能再次访问 """
3.线程锁
import threading,time #假设这是你的银行存款 balance = 0 #创建一个锁对象 lock = threading.Lock() def change(n): #先存后取,理论上上应该为0 global balance balance = balance + n balance = balance - n def run(m): for i in range(1000000): #获取锁/持有锁 lock.acquire() try: # 更改临界资源 change(m) #不管changge()有没有异常,finally中的代码都会被执行,锁需要被释放 finally: # 释放锁 lock.release() if __name__ == "__main__": t1 = threading.Thread(target=run,args=(5,)) t2 = threading.Thread(target=run, args=(8,)) t1.start() t2.start() t1.join() t2.join() print("balance=%d" % (balance))
import threading,time #假设这是你的银行存款 balance = 0 #创建一个锁对象 lock = threading.Lock() def change(n): #先存后取,理论上上应该为0 global balance balance = balance + n balance = balance - n def run(m): for i in range(1000000): #with lock的好处:可以自动上锁和解锁 with lock: change(m) if __name__ == "__main__": t1 = threading.Thread(target=run,args=(5,)) t2 = threading.Thread(target=run, args=(8,)) t1.start() t2.start() t1.join() t2.join() print("balance=%d" % (balance))
4.ThreadLocal
""" 一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见 不会影响其他线程,而全局变量则必须加锁 """ import threading balance = 0 #创建ThreadLocal对象,每个线程有独立的存储空间, # 每个线程对Threadlocal对象可以读写,但是互不影响 local = threading.local() def change(x,n): x = x + n x = x - n def run(m): """ local是一个全局变量,每个线程对象对local都可以读写y属性,但是互不影响 注意:y可以是一个任意的标识符,如local.a等 帮助理解:local是一个全局变量,local.y是每个线程的局部变量 可以任意读写而互不干扰,也不用管理锁 """ local.y = balance for i in range(1000000): change(local.y,m) if __name__ == "__main__": t1 = threading.Thread(target=run,args=(5,)) t2 = threading.Thread(target=run, args=(8,)) t1.start() t2.start() t1.join() t2.join() print("balance=%d" % (balance)) """ 多个线程访问同一个全局变量: a.线程锁 b.threadlocal:解决了参数在一个线程中各个函数之间相互传递的问题 """
5.定时线程
import threading def run(): print("hellohello") if __name__ == "__main__": print("父线程启动") #创建延时线程并启动 t = threading.Timer(5,run) t.start() print("父线程结束")
6.线程间的通信
#需求:一个线程向队列中添加数据,一个线程从队列中获取数据 """ 生产者消费者设计模式 是通过一个容器解决生产者和消费者之间的耦合问题, 生产者和消费者彼此之间不直接通讯,而是通过队列来进行通讯 所以生产者生产完数据之后不用等待消费者处理,,直接扔给队列, 消费者不找生产者获取数据,而是直接从队列中获取,队列相当于是一个缓冲区,平衡了生产者和消费者的能力 """ #进程间的通信:multiprocessing模块中的Queue import threading,queue,random,time def write(id,q): while True: num = random.randint(0,10000) q.put(num) print("生产者%d生产了数据%d放入了队列" % (id,num)) time.sleep(1) #任务完成 q.task_done() def read(id,q): while True: v = q.get() # if v is None: # break print("消费者%d消费了数据%d" % (id,v)) time.sleep(1) # 任务完成 q.task_done() if __name__ == "__main__": #获取队列 #栈【水杯】:先进后出,后进先出 队列【水平放置的水管】:先进先出,后进后出 q = queue.Queue() #启动生产者 for i in range(2): threading.Thread(target=write,args=(i,q)).start() # 启动消费者 for i in range(4): threading.Thread(target=read, args=(i,q)).start()
三、协程
#1. # def a(): # print(1) # b() # print(2) # b() # print(3) # b() # # def b(): # print("x") # print("y") # print("z") # # a() """ 1 x y z 2 x y z 3 x y z """ #2. """ 子程序/函数:一个线程就是执行一个子程序,子程序调用只有一个入口,一次返回,调用的顺序是明确的 协程:看上去也是子程序,但是执行过程中,在子程序的内部可以中断,然后转去执行别的子程序, 但是,这种情况不是函数的调用 """ def a(): print(1) yield "x" print(2) yield "y" print(3) yield "z" #协程的最简单的风格,控制函数的阶段执行,节约线程或者进程的切换 r = a() #print(r) print(next(r)) print(next(r)) print(next(r)) """ 1 x 2 y 3 z """ #与进程和线程相比,协程的执行效率较高, # 因为只有一个进程,只有一个线程,也不存在同时修改变量的冲突,使用共享西苑不需要锁,直接使用 print("=" * 30) #3.next()和send()可以触发yield的暂停 #next只能获取生成器中的数据,但是,send不但可以获取,还可以完成数据的传递 def run(): data = "" r = yield data print(1,r) r = yield "aaa" print(2, r) r = yield "bbb" print(3, r) r = yield "ccc" m = run() print(m) print("结果1:",m.send(None)) print("结果2:",m.send("111")) print("结果3:",m.send("222")) print("结果4:",m.send("333"))