Python 基础之 线程与进程
Python 基础之 线程与进程
在前面已经接触过了,socket编程的基础知识,也通过socketserver 模块实现了并发,也就是多个客户端可以给服务器端发送消息,那接下来还有个问题,如何用多线程与多进程来实现并发呢?今天就来了解一下这方面的知识。
一、进程与线程的概念介绍
多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务。Linux、windows就是支持多任务的操作系统,比起单任务系统它的功能增强了许多。
运行一个任务就需要cpu去处理,那同时运行多个任务就必须需要多个cpu?那如果有100个任务需要同时运行,就得买一个100核的cpu吗?显然不能! 现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
小结:一个cpu同一时刻只能运行一个“任务”;真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。
小结:
- 进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。
- 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
二、进程和线程的关系
进程是计算机中的程序关于某数据集上的一次运行活动(是代码执行的过程),是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。(进程是一个资源单位)
线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。(线程就是一个 执行单位)
我们用图片来形象的观察一下他们之间的关系:
小结:
-
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
-
CPU分给线程,即真正在CPU上运行的是线程。
三、并行和并发
并行(Parallel Processing)是指一个系统具有处理多个任务(动作)的能力,(1个CPU进行切换就可以完成的)
并发(concurrency Processing)指一个系统具有同时(唯一的时刻)处理多个任务(动作的)能力,(多核,eg:4核,4个CPU可同时处理1~4个任务的能力)。
并发是并行的子集。(并发是并行的子集。比如在单核CPU系统上,只可能存在并发而不可能存在并行。)
四、同步和异步
同步:当进程执行到一个IO(阻塞的时候),出现等待,这个就是同步。
异步:当进程执行到一个IO(阻塞的时候),不会等待,直到数据到来之后再回过头来处理,这就是异步。
五、多线程(threading.Thread)
线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。
Python的标准库提供了两个模块:_thread
和threading
,_thread
是低级模块,threading
是高级模块,对_thread
进行了封装。绝大多数情况下,我们只需要使用threading
这个高级模块。
1. 创建线程的方式:
1.直接创建:
import threading import time ########直接创建线程的方式############## def music(num): # 子线程的任务 print('您现在听到的音乐数目为:%s'%(num),time.ctime()) time.sleep(5) if __name__ == '__main__': t1 = threading.Thread(target=music,args=(100,)) #定义一个线程实例对象(此时没有任何现象产生) t1.start() # 启动的这一时刻才产生出了子线程(创建的那句任然是主线程的) print('你输入的密码有误,请重新输入',time.ctime()) # 主线程的代码
2.继承的方式创建:
#####################用面向对象的方式来创建线程################# ####继承的方式创建进程 # import threading # import time # class MyThread(threading.Thread): # def __init__(self,name,num): # threading.Thread.__init__(self) # self.name = name # self.num = num # def run(self): # print(' s% running on number :%s'%(self.name,self.num),time.ctime()) # time.sleep(3) # # if __name__ == '__main__': # t1 = MyThread('alex',12) # t2 = MyThread('egon',23) # t1.start() # t2.start() # print('ending.......',time.ctime())
主线程与子线程之间的关系:
2.. Thread类的实例方法
在Thread中有许多的实例方法我们依次介绍,先介绍一下join 和setDaemon方法:
#####################Thread类 中的实例方法############# ######对象.join() 这个子线程运行结束之前,之个子线程的父线程一直处于等待的过程##### #######对象.setDaemon(True) 讲线程设置成为守护线程,且必须在start()之前设置,谁是守护线程,则此守护线程结束的命运掌握在主线程手里。 import threading from time import ctime,sleep import time def ListenMusic(name): print ("Begin listening to %s. %s" %(name,ctime())) time.sleep(3) print("end listening %s"%ctime()) def RecordBlog(title): print ("Begin recording the %s! %s" %(title,ctime())) time.sleep(5) print('end recording %s'%ctime()) #######主线程的代码################################### threads = [] t1 = threading.Thread(target=ListenMusic,args=('水手',)) t2 = threading.Thread(target=RecordBlog,args=('python线程',)) threads.append(t1) threads.append(t2) if __name__ == '__main__': #t1.setDaemon(True) # 给t1加上守护线程没有用(t1执行的时间为3秒,t2执行时间为5秒,t1执行完了,主线程得等t2,所以t1会执行完的,) #t2.setDaemon(True) # 给t2加上守护线程,那么主线程关注的就是t1的情况了,只要保证t1执行完了,主线程也就可以结束了(因为t2是守护线程,只要主线程退出他就会退出) for t in threads: #t.setDaemon(True) #注意:一定在start之前设置 t.start() #触发了此句才开启了子线程 print(t.getName()) # print("count:",threading.active_count()) #查看此时此刻有几个线程在活动(主线程坑定算的) #t.join()#串行 #t.join() #获取的是t2的等待 print('我是主线程!!!',time.ctime()) while threading.active_count()==1: #若成立的话就说明此时此刻就剩下一个主线程了,子线程都结束了 print ("all over %s" %ctime())
其余方法介绍:
Thread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
按道理说多线程的使用肯定是可以提高执行的效率的,不过通过测试发现,这个确实没有达到想要的效果。有时还不如串行执行的效率高,这个过程到底出现了什么情况,是不是这个是一个假的多线程?这个只能呵呵吧。我们看一下在Python 中有一个叫GIL的东西(也就是全局解释器锁)。
3.那我们就看一下什么是GIL?
GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
在调用任何Python C API之前,要先获得GIL
GIL缺点:多处理器退化为单处理器;
优点:避免大量的加锁解锁操作。
3.1. GIL的影响
无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行。
所以,python是无法利用多核CPU实现多线程的。
这样,python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
#coding:utf8 from threading import Thread import time def counter(): i = 0 for _ in range(100000000): i = i + 1 return True def main(): l=[] start_time = time.time() for i in range(2): t = Thread(target=counter) t.start() l.append(t) t.join() for t in l: t.join() # counter() # counter() end_time = time.time() print("Total time: {}".format(end_time - start_time)) if __name__ == '__main__': main() ''' py2.7: 串行:9.17599987984s 并发:9.26799988747s py3.6: 串行:9.540389776229858s 并发:9.568442583084106s ''' 计算密集型,多线程并发相比串行,没有显著优势
3.2 how to sove ?
用multiprocessing替代Thread .multiprocessing库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。
#coding:utf8 from multiprocessing import Process import time def counter(): i = 0 for _ in range(100000000): i = i + 1 return True def main(): l=[] start_time = time.time() # for _ in range(2): # t=Process(target=counter) # t.start() # l.append(t) # #t.join() # # for t in l: # t.join() counter() counter() end_time = time.time() print("Total time: {}".format(end_time - start_time)) if __name__ == '__main__': main() ''' py2.7: 串行:8.92299985886 s 并行:8.19099998474 s py3.6: 串行:9.963459014892578 s 并发:5.1366541385650635 s ''' multiprocess多进程实现并发运算能够提升效率
当然multiprocessing也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住,三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。
小结:因为GIL的存在,只有IO Bound场景下的多线程会得到较好的性能提升;如果对并行计算性能较高的程序可以考虑把核心部分变成C模块,或者索性用其他语言实现;GIL在较长一段时间内将会继续存在,但是会不断对其进行改进。
六、同步锁
多线程和多进程最大的不同在于:多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。为了处理多线程中对公共资源的使用问题,引出了锁的概念。
import time import threading def subNum(): global num #在每个线程中都获取这个全局变量 temp = num time.sleep(0.1) num =temp-1 # 对此公共变量进行-1操作 num = 100 #设定一个共享变量 thread_list = [] for i in range(100): t = threading.Thread(target=subNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('Result: ', num) #运行结果: #Result: 99 多线程共享变量,无法保证变量安全
如上实例,在一个进程内,设置共享变量num=100,然后创建100个线程,执行num-=1的操作,但是,由于在函数subNum中存在time.sleep(0.1),该语句可以等价于IO操作。于是在这短短的0.1秒的时间内,所有的线程已经创建并启动,拿到了num=100的变量,等待0.1秒过后,最终得到的num其实是99.
锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:
import time import threading def subNum(): global num #在每个线程中都获取这个全局变量 lock.acquire() temp = num time.sleep(0.1) num =temp-1 # 对此公共变量进行-1操作 lock.release() num = 100 #设定一个共享变量 lock = threading.Lock() #生成一个同步锁对象 thread_list = [] for i in range(100): t = threading.Thread(target=subNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('Result: ', num) #运行结果: #Result: 0 应用lock方法,保证变量安全
ock.acquire()与lock.release()包起来的代码段,保证同一时刻只允许一个线程引用。
import threading R=threading.Lock() R.acquire() ''' 对公共数据的操作 ''' R.release()
七、死锁与递归锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
import threading import time mutexA = threading.Lock() mutexB = threading.Lock() class MyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): self.fun1() self.fun2() def fun1(self): mutexA.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放 print("I am %s , get res: %s---%s" %(self.name, "ResA",time.time())) mutexB.acquire() print("I am %s , get res: %s---%s" %(self.name, "ResB",time.time())) mutexB.release() mutexA.release() def fun2(self): mutexB.acquire() print("I am %s , get res: %s---%s" %(self.name, "ResB",time.time())) time.sleep(0.2) mutexA.acquire() print("I am %s , get res: %s---%s" %(self.name, "ResA",time.time())) mutexA.release() mutexB.release() if __name__ == "__main__": print("start---------------------------%s"%time.time()) for i in range(0, 10): my_thread = MyThread() my_thread.start() #运行结果: #start---------------------------1494316634.4121563 #I am Thread-1 , get res: ResA---1494316634.4121563 #I am Thread-1 , get res: ResB---1494316634.4121563 #I am Thread-1 , get res: ResB---1494316634.4121563 #I am Thread-2 , get res: ResA---1494316634.4121563 死锁实例
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
import threading import time # mutexA = threading.Lock() # mutexB = threading.Lock() rlock = threading.RLock() class MyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): self.fun1() self.fun2() def fun1(self): rlock.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放 print("I am %s , get res: %s---%s" %(self.name, "ResA",time.time())) rlock.acquire() print("I am %s , get res: %s---%s" %(self.name, "ResB",time.time())) rlock.release() rlock.release() def fun2(self): rlock.acquire() print("I am %s , get res: %s---%s" %(self.name, "ResB",time.time())) time.sleep(0.2) rlock.acquire() print("I am %s , get res: %s---%s" %(self.name, "ResA",time.time())) rlock.release() rlock.release() if __name__ == "__main__": print("start---------------------------%s"%time.time()) for i in range(0, 10): my_thread = MyThread() my_thread.start() #运行结果:从以下结果也可以发现,线程之间是竞争关系 """ start---------------------------1494316940.0863945 I am Thread-1 , get res: ResA---1494316940.0873976 I am Thread-1 , get res: ResB---1494316940.0873976 I am Thread-1 , get res: ResB---1494316940.0873976 I am Thread-1 , get res: ResA---1494316940.287911 I am Thread-2 , get res: ResA---1494316940.287911 I am Thread-2 , get res: ResB---1494316940.287911 I am Thread-2 , get res: ResB---1494316940.287911 I am Thread-2 , get res: ResA---1494316940.4883447 I am Thread-4 , get res: ResA---1494316940.4883447 I am Thread-4 , get res: ResB---1494316940.4883447 I am Thread-4 , get res: ResB---1494316940.4883447 I am Thread-4 , get res: ResA---1494316940.6886203 I am Thread-6 , get res: ResA---1494316940.6886203 I am Thread-6 , get res: ResB---1494316940.6896234 I am Thread-6 , get res: ResB---1494316940.6896234 I am Thread-6 , get res: ResA---1494316940.890659 I am Thread-8 , get res: ResA---1494316940.890659 I am Thread-8 , get res: ResB---1494316940.890659 I am Thread-8 , get res: ResB---1494316940.890659 I am Thread-8 , get res: ResA---1494316941.0918815 I am Thread-10 , get res: ResA---1494316941.0918815 I am Thread-10 , get res: ResB---1494316941.0918815 I am Thread-10 , get res: ResB---1494316941.0918815 I am Thread-10 , get res: ResA---1494316941.2923715 I am Thread-5 , get res: ResA---1494316941.2923715 I am Thread-5 , get res: ResB---1494316941.2923715 I am Thread-5 , get res: ResB---1494316941.2923715 I am Thread-5 , get res: ResA---1494316941.493138 I am Thread-9 , get res: ResA---1494316941.493138 I am Thread-9 , get res: ResB---1494316941.493138 I am Thread-9 , get res: ResB---1494316941.493138 I am Thread-9 , get res: ResA---1494316941.6937861 I am Thread-7 , get res: ResA---1494316941.6937861 I am Thread-7 , get res: ResB---1494316941.6937861 I am Thread-7 , get res: ResB---1494316941.6937861 I am Thread-7 , get res: ResA---1494316941.8946414 I am Thread-3 , get res: ResA---1494316941.8946414 I am Thread-3 , get res: ResB---1494316941.8946414 I am Thread-3 , get res: ResB---1494316941.8946414 I am Thread-3 , get res: ResA---1494316942.0956843 """ 递归锁解决死锁
八、event对象
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event对象中的信号标志被设置为False。如果有线程等待一个Event对象, 而这个Event对象的标志为False,那么这个线程将会被一直阻塞直至该标志为True。一个线程如果将一个Event对象的信号标志设置为True,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行。
################event同步对象############# import threading,time class Boss(threading.Thread): def run(self): print("BOSS:今晚大家都要加班到22:00。") print(event.isSet())#执行结果为: False event.set() time.sleep(5) print("BOSS:<22:00>可以下班了。") print(event.isSet()) event.set() # 此方法就是在 设定 类似于(标志位)的东西。 class Worker(threading.Thread): def run(self): event.wait()#(在boss类中有一个set设定后就不等待了) 是等待的意思,是在等待类似于(标志位)的东西,一旦这个东西被设定了就不会再等待了 # 一旦event被设定,等同于pass;反之若没有被设定就相当于一个阻塞的状态; print("Worker:哎……命苦啊!") time.sleep(1) event.clear() # 用来清除 此标志位 event.wait() # 此时清空的标志位 ,没有设定了,此时又在等待 print("Worker:OhYeah!") if __name__=="__main__": event=threading.Event() # 先创建一个event 同步对象 threads=[] for i in range(5): threads.append(Worker()) # 将五个Worker类的实例对象添加到列表threads中去 threads.append(Boss()) # 创建了一个老板对象 for t in threads: t.start() for t in threads: t.join() ################5个工人对象加一个老板对象再加一个主线程对象,这七个线程一起进行运行######### print("ending.....")
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。
九、队列(一种数据结构)
import queue # # q = queue.Queue(5) # # 每一条get()语句后面都需要一条task_done (表示某项任务完成) # q.put(10) # 往队列中存放值 # q.put(20) # print(q.get()) # q.task_done() # print(q.get()) # q.task_done() # q.join() # 等到队列为空再执行别的操作 # print('ending.......') #队列存取数据的几种模式########## ############先进先出模式########################## # import queue # 线程队列 # # q=queue.Queue() # FIFO模式(先进先出) # # q.put(12) # q.put("hello") # q.put({"name":"yuan"}) # q.put_nowait(56)# q.put(block=False) # # print(q.qsize()) #返回队列的大小 # print(q.empty()) # print(q.full()) # # # # while 1: # data=q.get() # print(data) # print("----------") ###################队列先进后出模式#################### #先进后出 # import queue # # q=queue.LifoQueue() # # q.put(12) # 存储数据 # q.put("hello") # q.put({"name":"yuan"}) # # while 1: # data=q.get() # 获取数据 # print(data) # print("----------") #####################按优先级的顺序读取################### q=queue.PriorityQueue() q.put([3,12]) q.put([2,"hello"]) q.put([4,{"name":"yuan"}]) while 1: data=q.get() print(data[1]) print("----------")
十、多进程(multiprocessing)
由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。
multiprocessing包是python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。
1.创建方式:
#################第一种创建方式################### # Process 类调用的方式 from multiprocessing import Process import time def f(name): print('hello',name,time.ctime()) time.sleep(1) if __name__ == '__main__': p_list = [] for i in range(3): p = Process(target= f,args = ('SB:%s'%i,)) p_list.append(p) p.start() for i in p_list: p.join() print('ending............',time.ctime()) #######################继承Process类调用####################### from multiprocessing import Process import time class MyProcess(Process): def __init__(self,name): super(MyProcess,self).__init__() self.name = name def run(self): print('hello',self.name,time.ctime()) time.sleep(1) if __name__ == '__main__': p_list = [] for i in range(3): p = MyProcess('SB') p.start() p_list.append(p) for i in p_list: i.join() print('ending.........',time.ctime())
process类: 构造方法: Process([group [, target [, name [, args [, kwargs]]]]]) group: 线程组,目前还没有实现,库引用中提示必须是None; target: 要执行的方法; name: 进程名; args/kwargs: 要传入方法的参数。 实例方法: is_alive():返回进程是否在运行。 join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。 start():进程准备就绪,等待CPU调度 run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。 terminate():不管任务是否完成,立即停止工作进程 属性: daemon:和线程的setDeamon功能一样 name:进程名字。 pid:进程号。
2.进程间通信的方式:
#通过进程队列来实现进程之间的通信########### # from multiprocessing import Process,Queue # import queue # # def f(q,n): # q.put(n*(n+1)) # print('son process is',id(q)) # # # # if __name__ == '__main__': # q = Queue() #创建了一个进程队列 # print('main process is',id(q)) # for i in range(3): # p = Process(target=f,args=(q,i))# 讲三个进程对象和进程队列传递到子进程中去 # p.start() # print(q.get()) # print(q.get()) # print(q.get()) #########################通过双向管道的形式进行通信#################### from multiprocessing import Process, Pipe def f(conn): conn.send([12, {"name":"yuan"}, 'hello']) response=conn.recv() print("response",response) conn.close() print("q_ID2:",id(conn)) if __name__ == '__main__': parent_conn, child_conn = Pipe() #双向管道 (既可以发送消息、也可以接受消息) print("q_ID1:",id(child_conn)) p = Process(target=f, args=(child_conn,)) # 创建子进程 对象将 子进程的管道也传输给子进程实现子进程与主进程之间的通信 p.start() print(parent_conn.recv()) # prints "[42, None, 'hello']" parent_conn.send("儿子你好!") ###########################通过 manager 实现通信及数据共享############# from multiprocessing import Process, Manager def f(d, l,n): d[n] = '1' #{0:"1"} d['2'] = 2 #{0:"1","2":2} l.append(n) #[0,1,2,3,4, 0,1,2,3,4,5,6,7,8,9] #print(l) if __name__ == '__main__': with Manager() as manager: # 创建了一个manager 的对象 d = manager.dict()#{} #通过自己的方式创建一个manager类型的字典 l = manager.list(range(5))#[0,1,2,3,4] p_list = [] for i in range(10): p = Process(target=f, args=(d,l,i)) p.start() p_list.append(p) for res in p_list: res.join() print(d) print(l)
3.进程池:
############# 进程池############## from multiprocessing import Pool import time def foo(args): time.sleep(1) print(args,time.ctime()) if __name__ == '__main__': p = Pool(5) # 池中限定的最大进程数 for i in range(30): p.apply_async(func = foo,args=(i,)) # 从池子中去取一个异步进程并执行 p.close() # 等待子进程执行完毕后关闭线程池 p.join() ##############################################进程池例子2################ from multiprocessing import Process,Pool import time,os def Foo(i): time.sleep(1) print(i) print("son",os.getpid()) return "HELLO %s"%i def Bar(arg): # 主进程的函数(主进程的任务) print(arg) print("hello") print("Bar:",os.getpid()) if __name__ == '__main__': pool = Pool(5) # 进程池中最多有5个进程 print("main pid",os.getpid()) for i in range(100): # pool.apply(func=Foo, args=(i,)) #同步接口 #pool.apply_async(func=Foo, args=(i,)) #回调函数: 就是某个动作或者函数执行成功后再去执行的函数 pool.apply_async(func=Foo, args=(i,),callback=Bar) #Bar 就是回调函数 pool.close() pool.join() # join与close调用顺序是固定的 print('end')
十一、协程
回顾一下线程和进程,线程是无法利用多核,so:无法实现正真意义上的并行效果。但是处理IO密集型任务(网络传输、web应用),有显著效果。
协程:顾名思义就是协作的意思,(非占用式的),他的内部也存在切换不过是自己可以控制何时切换。
一、.实现方式:
1.通过 迭代器实现协程。
import time import queue def consumer(name): # consumer 此时是一个生成器 print("--->ready to eat baozi...") while True: new_baozi = yield print("[%s] is eating baozi %s" % (name,new_baozi)) time.sleep(1) def producer(): r = conn1.__next__() # 此时执行conn1的next方法,就会执行此生成器 r = conn2.__next__() # 此时执行conn1的next方法,就会执行此生成器 n = 0 while 1: time.sleep(1) print("\033[32;1m[producer]\033[0m is making baozi %s and %s" %(n,n+1) ) conn1.send(n) # 通过 send()就可以切换到 consumer 进行执行 conn2.send(n+1) n +=2 if __name__ == '__main__': conn1 = consumer("c1") #生成了一个生成器对象conn1,只是一个对象,什么作用也没有(想使用就要使用其next方法) conn2 = consumer("c2") #生成了一个生成器对象conn2,只是一个对象,什么作用也没有(想使用就要使用其next方法) producer() # producer 只是一个普通的函数,此句就是表示执行这个函数
2.greenlet:
greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。greentlet是python中实现我们所谓的"Coroutine(协程)"的一个基础库.
from greenlet import greenlet def test1(): print(12) gr2.switch() print(34) gr2.switch() def test2(): print(56) gr1.switch() print(78) gr1 = greenlet(test1) # gr1 绑定 函数test1 gr2 = greenlet(test2) # gr1 绑定 函数test2 print(gr1) # <greenlet.greenlet object at 0x0070B620> gr1.switch() # switch 是启动切换的意思
3.基于greenlet的框架——gevent
gevent模块实现协程:
Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。
gevent是第三方库,通过greenlet实现协程,其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:
import gevent import requests,time start=time.time() def f(url): print('GET: %s' % url) resp =requests.get(url) data = resp.text print('%d bytes received from %s.' % (len(data), url)) ###############使用串行的方式执行############## # f('https://www.python.org/') # f('https://www.yahoo.com/') # f('https://www.baidu.com/') # f('https://www.sina.com.cn/') # f("http://www.xiaohuar.com/hua/") ##################使用协程的方式执行#################### gevent.joinall([ gevent.spawn(f, 'https://www.python.org/'), gevent.spawn(f, 'https://www.yahoo.com/'), gevent.spawn(f, 'https://www.baidu.com/'), gevent.spawn(f, 'https://www.sina.com.cn/'), gevent.spawn(f, 'http://www.xiaohuar.com/hua/'), ]) print("cost time:",time.time()-start)
好了,关于线程和进程以及协程的例子就介绍到这里,下一篇我们会着重介绍一下IO常用的几种模型。