版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第15章进程和线程1概述进程和线程是应多任务需求而产生的,现代的操作系统都是支持多任务的“操作系统”,对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。2概述由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,即一个进程有多个线程。多进程和多线程的执行方式是一样的,就是由操作系统在多个进程(线程)之间快速切换,让每个进程(线程)都短暂地交替运行,看起来就像同时执行一样。Python既支持多进程,又支持多线程,开发者可以根据具体任务情况,综合考虑是采用多进程还是多线程的方式完成多任务情景。315.1创建进程计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到内存中并被操作系统调用,才拥有其生命期。进程(有时称为重量级进程)则是一个执行中的程序,每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。操作系统管理其上所有进程的执行,并为这些进程合理地分配时间。由于每个进程都拥有自己的内存和数据栈等,所以进程没有任何共享状态,进程修改的数据,改动仅限于该进程内,进程间只能采用进程间通信(IPC)的方式共享信息。415.1.1multiprocessing模块multiprocessing模块是一个跨平台版本的多进程模块,允许为多核或多CPU派生进程,multiprocessing模块的功能众多:提供了Process、Queue、Pipe、Pool等组件类。515.1.2Process类multiprocessing模块提供了一个Process类来创建进程对象。用法:Process(target,name,args,kwargs={}):参数:target:表示调用对象,如函数、方法等,即子进程要执行的任务;args:表示调用对象的位置参数元组,args=(1,2);kwargs:表示调用对象的字典参数,kwargs={‘name’:‘张三,‘age’:18};name:为子进程的名称。Process对象具有name和pid属性,分别代表进程的名称和进程的pid。Process有四个常用的方法:通过start()方法启动进程;通过terminate()方法强制终止进程,不会进行任何清理操作,如果该进程获得了一个锁那么锁将不会被释放,进而导致死锁;通过join()方法使得主线程等待当前子进程终止;通过is_alive()方法判断进程是否仍然处于运行状态。615.1.2Process类>>>importmultiprocessing>>>importtime>>>importos>>>defpro(number):print('进程{}isrunning!'.format(number))time.sleep(1)>>>defmain():i=1print('主进程id:',os.getpid())whilei<=3:p=multiprocessing.Process(target=pro,args=(i,))pro(i)#由于在window下子进程信息无法被输出,所以此处用该语句模拟子进程的输出,在Unix/Linux下或真实环境中不需要该语句p.start()p.join()print('进程{},name:{},pid:{},end!'.format(i,,p.pid))
i=i+17>>>main()主进程id:15184进程1isrunning!进程1,name:Process-1,pid:15248,end!进程2isrunning!进程2,name:Process-2,pid:11312,end!进程3isrunning!进程3,name:Process-3,pid:13260,end!15.1.3Pool进程池如果要启动大量的子进程,可以用进程池的方式批量创建子进程。Pool可以提供指定数量的进程供用户使用,默认是CPU核数。当有新的请求提交到Pool的时候,如果池子没有满,会创建一个进程来执行,否则就会让该请求等待。创建进程池用法:Pool([numprocess[,initializer[,initargs]]])参数:numprocess:进程池中进程数,默认是cpu的核数,即os.cpu_count()的值;initializer:是每个工作进程启动时要执行的可调用对象,默认为None;initargs:是要传给initializer的参数组。815.1.3Pool进程池进程池对象拥有一系列的方法,常用的方法如下:(1)apply(func[,args[,kwds]])方法,占用进程池中的一个进程,使用参数args和kwds调用func,其中,args为一个元组,kwds为一个字典(以参数名为键),然后返回结果。该方法将阻塞调用进程直至func执行完成。即同时只执行一个子进程。(2)apply_async()方法:为apply()的异步版本,使用该方法时,会立即返回,不会阻塞调用进程,进程池中可以有numprocess个进程并行。(3)close()方法:阻止新的任务被提交到池中。一旦完成所有任务,子进程将退出。(4)terminate()方法:立即停止工作进程,不必等待任务完成。(5)join()方法:等待所有的子进程执行完毕。在调用join之前必须先调用close或terminate。915.1.3Pool进程池例15-1同步进程池示例,代码如pool.py所示。#pool.pyfrommultiprocessingimportPoolimporttime,datetimedeftest(p):print(p)time.sleep(1)if__name__=="__main__":pool=Pool(processes=2)print('循环开始时间:{}'.format(datetime.datetime.now().strftime('%H:%M:%S')))foriinrange(10):pool.apply(test,args=(i,))#同步方法,总数为2,执行总数为1,当一个进程执行完后启动一个新进程,即同时只执行一个子进程
print('循环结束时间:{}'.format(datetime.datetime.now().strftime('%H:%M:%S')))pool.close()pool.join()print('任务完成时间:{}'.format(datetime.datetime.now().strftime('%H:%M:%S')))1015.1.3Pool进程池运行结果如下,可以看出10次循环的开始与结束时间间隔为10秒,由于每个子进程执行时间为1秒,所以不存在并行;循环结束时间与任务完成时间一致,说明子进程会阻塞主进程,直至所有子进程执行完毕。循环开始时间:18:19:10循环结束时间:18:19:20任务完成时间:18:19:201115.1.3Pool进程池例15-2异步进程池示例,代码如pool-async.py所示。#pool-async.pyfrommultiprocessingimportPoolimporttime,datetimedeftest(p):print(p)time.sleep(1)if__name__=="__main__":pool=Pool(processes=2)print('循环开始时间:{}'.format(datetime.datetime.now().strftime('%H:%M:%S')))foriinrange(10):pool.apply_async(test,args=(i,))#异步方法,执行的进程总数为2,当一个进程执行完后启动一个新进程。
print('循环结束时间:{}'.format(datetime.datetime.now().strftime('%H:%M:%S')))pool.close()pool.join()print('任务完成时间:{}'.format(datetime.datetime.now().strftime('%H:%M:%S')))1215.1.3Pool进程池运行结果如下,可以看出10次循环的开始与任务完成时间间隔为5秒,由于每个子进程执行时间为1秒,所以同时有两个进程在并行执行;循环结束时间与任务完成时间间隔5秒,说明主进程并未等待子进程任务完成,即子进程没有阻塞主进程的执行。循环开始时间:18:24:25循环结束时间:18:24:25任务完成时间:18:24:301315.2进程之间的通信进程的数据不存在共享状态,但是进程之间肯定是需要通信的,multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来实现进程间的通信。1415.2.1QueueQueue用来创建共享的进程队列的类,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。用法Queue([maxsize])参数maxsize是队列中允许最大项数,省略则无大小限制。通过Queue对象的方法,可以实现对队列的操作,常用方法如下:(1)put()方法:行队列中存入元素;(2)get()方法:从队列读取并且删除一个元素;(3)get_nowait():同get(False);(4)put_nowait():同put(False);(5)empty():判断队列是否为空,如果为空,返回True,否则返回False;(6)full():判断队列是否已满,如果已满,返回True,否则返回False;(7)qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样。1515.2.1Queue例15-3用多进程队列实现在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据,代码如processQueue.py所示。#processQueue.pyfrommultiprocessingimportProcess,Queueimportos,time,random
#写数据进程执行的代码:defwrite(q):print('写进程已启动:')forvaluein['A','B','C']:print('Put%stoqueue...'%value)q.put(value)time.sleep(1)
return1615.2.1Queue#读数据进程执行的代码:defread(q):print('读进程已启动:')whileTrue:value=q.get(True)print('Get%sfromqueue.'%value)
if__name__=='__main__':#父进程创建Queue,并传给各个子进程:q=Queue()pw=Process(target=write,args=(q,))pr=Process(target=read,args=(q,))#启动子进程pw,写入:write(q)#由于在window下子进程信息无法被输出,所以此处用该语句模拟子进程的输出,在Unix/Linux下或真实环境中不需要该语句print(q.qsize())#由于以上模拟,队列中已被写入的3个元素1715.2.1Queue#启动子进程pw,写入:pw.start()#等待pw结束:pw.join()print(q.qsize())#启动子进程后,队列中又被写入的3个元素,所以此时队列中已有6个元素
#启动子进程pr,读取:read(q)#由于在window下子进程信息无法被输出,所以此处用该语句模拟子进程的输出,在Unix/Linux下或真实环境中不需要该语句pr.start()#pr进程里是死循环,无法等待其结束,只能强行终止:pr.terminate()1815.2.1Queue在IDLE中的运行结果如下:写进程已启动:PutAtoqueue...PutBtoqueue...PutCtoqueue...36读进程已启动:GetAfromqueue.GetBfromqueue.GetCfromqueue.GetAfromqueue.GetBfromqueue.GetCfromqueue.1915.2.1Queue另外,put方法有两个可选参数:blocked和timeout,如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间;如果超时,会抛出Queue.Full异常;如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。get方法也有blocked和timeout两个可选参数,如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常;blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常。empty和full方法返回的结果是不可靠的,比如在方法返回的过程中,队列中又加入或删除了元素就会导致结果的不准确。使用Queue实现进程间通信时还需注意:进程中的信息在window系统中是不会被打印显示的,所以建议使用unix、linux、mac操作系统来做此模块的练习。2015.2.2Pipemultiprocessing还提供了另外一种进程通信方式:Pipe(管道)方式,即一端发送另一端接收。Pipe分为单向管道和双向管道,默认为双向管道。用法:Pipe([duplex])Pipe方法将返回2个连接对象组成的元组(conn1,conn2),代表管道的两端,参数duplex默认为True,是双向管道,conn1、conn2均可收发消息。如果duplex为False,则为单向管道,其conn1只能用来接收消息,conn2只能用来发送消息。连接对象conn1、conn2通过send和recv方法来发送和接受消息。2115.2.2Pipe例15-4创建两个子进程,一个子进程通过Pipe发送数据,一个子进程通过Pipe接收数据,代码如pipe.py所示。#pipe.pyfrommultiprocessingimportProcess,Pipeimportrandom,time,os
defproc_send(pipe,urls):forurlinurls:print("Processsend:%s"%(url))pipe.send(url)time.sleep(1)
defproc_recv(pipe):whileTrue:print("Processrev:%s"%(pipe.recv()))time.sleep(1)2215.2.2Pipedefmain():pipe=Pipe()print(type(pipe))#返回元组p1=Process(target=proc_send,args=(pipe[0],['url_'+str(i)foriinrange(3)]))proc_send(pipe[0],['url_'+str(i)foriinrange(3)])#window下模拟子进程输出p2=Process(target=proc_recv,args=(pipe[1],))proc_recv(pipe[1])#window下模拟子进程输出p1.start()p2.start()p1.join()p2.join()
main()2315.2.2Pipe运行结果如下:<class'tuple'>Processsend:url_0Processsend:url_1Processsend:url_2Processrev:url_0Processrev:url_1Processrev:url_22415.3创建线程多任务可以由多进程完成,也可以由一个进程内的多线程完成。Python的标准库提供了两个多线程模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。2515.3.1threading模块threading模块提供了一些线程相关的类,包括Thread基本线程类和线程锁相关的类,将在后续的章节着重介绍Thread类和线程锁。另外threading模块还提供了一些实用的方法,常用方法如下:(1)threading.active_count():返回当前存活的线程类Thread对象。返回的计数等于enumerate()返回的列表长度。(2)threading.current_thread():返回当前对应调用者的控制线程的Thread对象。(3)threading.get_ident():返回当前线程的“线程标识符”。(4)threading.enumerate():以列表形式返回当前所有存活的Thread对象。(5)threading.main_thread():返回主Thread对象。一般情况下,主线程是Python解释器开始时创建的线程。2615.3.1threading模块>>>importthreading>>>threading.active_count()2>>>threading.current_thread()<_MainThread(MainThread,started18348)>>>>threading.enumerate()[<_MainThread(MainThread,started18348)>,<Thread(SockThread,starteddaemon21796)>]>>>threading.get_ident()18348>>>threading.main_thread()<_MainThread(MainThread,started18348)>2715.3.2Thread类创建多线程的最简单直接的方法是实例化threading.Thread类的对象,将线程要执行的任务函数作为参数传入线程。用法:threading.Thread(target,name,agrs,kwargs)其中参数target是一个可调用对象,在线程启动后执行;参数name是线程的名字,默认值为“Thread-N“,N是一个数字;参数args和kwargs分别表示调用target时的参数列表和关键字参数字典。2815.3.2Thread类Thread类提供了对象的常用方法,用于对线程的操作,常用的对象方法如下:start():启动线程活动;run():用以表示线程活动的方法,线程被cpu调度后自动执行的方法;join([time]):调用该方法会使主调线程堵塞,直到被调用线程运行结束或者超时。参数time表示超时时间,如果未提供该参数,那么主调线程将一直堵塞到被调线程执行结束;isAlive():返回线程是否是活动的。getName():返回线程名。setName():设置线程名。2915.3.2Thread类例15-5用Thread类创建线程示例,代码如threading.py所示。#threading.pyimportthreadingimporttime
defdoWaiting():print("子线程{}开始等待:{}".format(threading.current_thread().getName(),time.strftime("%H:%M:%S")))time.sleep(3)print("子线程{}结束等待:{}".format(threading.current_thread().getName(),time.strftime("%H:%M:%S")))
t=threading.Thread(target=doWaiting,name='subthread')
t.start()time.sleep(1)#确保线程t已经启动
print("开始阻塞主线程,等待子线程执行")t.join()#join方法将一直堵塞主线程,直到t运行结束print("子线程执行完,结束阻塞,主线程继续执行!")3015.3.2Thread类运行结果如下:子线程subthread开始等待:21:20:24开始阻塞主线程,等待子线程执行子线程subthread结束等待:21:20:27子线程执行完,结束阻塞,主线程继续执行!另一种创建多线程的方法是通过继承Thread类,并重写它的run()方法来自定义线程类,通过自定义线程类来创建多线程任务,对该方法感兴趣的读者可以自行查阅资料,此处不做介绍。3115.4线程同步15.4.1多线程抢夺变量多线程和多进程最大的不同在于,在多进程中,进程修改的数据,改动仅限于该进程内,同一个变量,各自有一份拷贝存在于每个进程中,进程间互不影响;而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。3215.4.1多线程抢夺变量例15-6多线程共享变量时的问题,如代码threadProblem.py所示。#threadProblem.pyimportthreadingimporttimea=0defadd1():globalatmp=a+1time.sleep(0.2)#延时0.2秒,模拟写入所需时间a=tmpprint('%saddsato1:%d'%(threading.current_thread().getName(),a))
threads=[threading.Thread(name='t%d'%(i,),target=add1)foriinrange(10)][t.start()fortinthreads]3315.4.1多线程抢夺变量运行结果如下所示。正常情况,在执行了10个子线程后,共享变量a的值应由0变为10,但结果a的值始终为1,只相当于一个线程执行的结果。这是因为在修改a前,有0.2秒的休眠时间,某个线程延时后,CPU立即分配计算资源给其他线程。直到分配给所有线程后,根据结果反映出,0.2秒的休眠时长还没耗尽,这样每个线程get到的a值都是0,所以才出现上面的结果。另外,由于时间片的切换导致t9线程在打印完字符内容后,没有来得及打印换行符就把时间片给了t1线程,所以直到t2线程打印结束后,t9线程才再次获得时间片,这时才打印出t9线程的换行符。3415.4.1多线程抢夺变量t0addsato1:1t5addsato1:1t3addsato1:1t7addsato1:1t9addsato1:1t1addsato1:1#t9未打印完,时间片切换,t9打印换行符的操作被挂起。t2addsato1:1(此处为打印了换行符的空行)#t9第二次获得时间片,打印出换行符t8addsato1:1t4addsato1:1t6addsato1:13515.4.2线程锁多线程抢夺变量,导致变量修改混乱的原因是,所有线程可以随时获取变量值,并可以随时修改变量值。若某一个线程要修改变量时,其他对变量的读写操作都必须等到该线程修改变量的操作完成后再进行,就不会出现修改混乱了。python中提供的锁机制,即某段代码只能单线程执行时,上锁,其他线程等待,直到释放锁后,其他线程再争锁,执行代码,释放锁,重复以上。3615.4.2线程锁用锁机制修改例15-6的代码如threadProblem_change.py所示:#threadProblem_change.pyimportthreadingimporttimelocka=threading.Lock()#创建一把锁a=0#共享变量adefadd1():globalatry:locka.acquire()#获得锁,其他线程被堵塞tmp=a+1time.sleep(0.2)#延时0.2秒,模拟写入所需时间a=tmpfinally:locka.release()#释放锁,其他线程可以抢夺锁
print('%saddsato1:%d'%(threading.current_thread().getName(),a))threads=[threading.Thread(name='t%d'%(i,),target=add1)foriinrange(10)][t.start()fortinthreads]3715.4.2线程锁运行结果如下,结果不再出现混乱。t0addsato1:1t1addsato1:2t2addsato1:3t3addsato1:4t4addsato1:5t5addsato1:6t6addsato1:7t7addsato1:8t8addsato1:9t9addsato1:103815.4.3线程优先队列Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 武汉地区高校(公共)体育教师专业发展的多维度审视与提升路径研究
- 次贷衍生品价值剖析与影响因素的实证探究
- 模因论赋能:高中英语写作教学的创新实验与成效探究
- 足掌瘢痕的护理
- 雨课堂学堂在线学堂云土木工程材料(武汉大学)单元测试考核答案
- 浙江省金华十校2026年4月高三模拟考试历史+答案
- 甲基氧化偶氮甲基醋酸酯(CAS号:592-62-1)理化性质与危险特性一览表
- 业务合规建设承诺函(8篇)
- 社会责任项目推广交流活动方案
- 企业行政管理流程管理手册
- 2026广西壮族自治区供销合作联社直属院校公开招聘工作人员63人考试参考题库及答案解析
- 山西省吕梁市2025-2026学年高三第二次模拟调研测试生物+答案
- 2026山东青岛海上综合试验场有限公司招聘38人备考题库含完整答案详解(全优)
- 2026年中国航空电机系统行业市场现状、前景分析研究报告(智研咨询发布)
- 2026年上半年中小学教师资格考试教育知识与能力(中学)真题附答案解析
- 2025特变电工校园招聘200人笔试历年常考点试题专练附带答案详解2套试卷
- GB/T 40740-2021堆焊工艺评定试验
- GB/T 30451-2013有序介孔二氧化硅
- GB/T 13173.2-2000洗涤剂中总活性物含量的测定
- 宾语从句习题
- 三爱三节主题班会 (1)课件
评论
0/150
提交评论