进程 线程 协程
线程
Python threading模块
线程有2种调用方式,如下:
import threading import time def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print(t1.getName()) #获取线程名 print(t2.getName())
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数 print("running on number:%s" %self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()
join和setDaemon
主线程 : 当一个程序启动时 , 就有一个进程被操作系统创建 , 与此同时一个线程也立刻运行 , 该线程通常叫做程序的主线程
子线程 : 因为程序是开始时就执行的 , 如果你需要再创建线程 , 那么创建的线程就是这个主线程的子线程
join的作用:是保证当前线程执行完成后,再执行其它线程
1 import threading 2 import time 3 4 def run(n): 5 print("task ",n ) 6 time.sleep(2) 7 8 start_time = time.time() 9 t_objs = [] #存线程实例 10 11 for i in range(50): #生成50个线程 12 t = threading.Thread(target=run,args=("t-%s" %i ,)) 13 t.start() 14 t_objs.append(t) #为了不阻塞后面线程的启动,不在这里join,先放到一个列表里 15 16 for t in t_objs: #循环线程实例列表,等待所有线程执行完毕 17 t.join() 18 19 print("---all threads has finished...") 20 print("cost:",time.time() - start_time)
setDaemon
将线程声明为守护线程,必须在start() 方法调用之前设
setDaemon(),只要主线程完成了,不管子线程是否完成,都要和主线程一起退出
import threading import time def run(n): print('task',n) time.sleep(2) print('i am 子线程') #主线程结束,setDaemon不管有没有运行完都会被销毁 if __name__ == '__main__': t1 = threading.Thread(target=run,args=(1,)) t2 = threading.Thread(target=run,args=(2,)) t1.setDaemon(True) #设置守护线程,放在start之前 t1.start() t2.setDaemon(True) t2.start() print('I am main thread') 结果: task 1 task 2 I am main thread
queue(队列)
import queue #queue #主要作用:解耦 # 提高运行效率 q =queue.Queue() #生成一个队列对象 先入先出 q.put('1') #put item into the queue q.put('2') q.put('3') q.qsize() #看队列大小 q.get() #从队列中取 q.get(block=True, timeout=None) #取不到数据,默认阻塞,timeout设置阻塞时间 q.get_nowait() #如果队列为空,取不到数据,抛出异常,不会阻塞卡主 q = queue.Queue(maxsize=3) #maxsize可以设置队列的大小,最多允许存三个 q = queue.PriorityQueue #优先级 print(q.full()) #判断队列是否有数据 返回blue值 print(q.empty()) #判断队列是否是空 返回blue值
生产者消费者模型
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
import threading,time import queue q = queue.Queue(maxsize=5) #设置maxsize=5,防止生产过快 def Producer(name): #生产者 count = 1 while True: q.put("面包%s" % count) print("%s生产了面包%s"%(name,count)) count +=1 time.sleep(0.1) def Consumer(name): #消费者 while True: print("[%s] 取到[%s] 并且吃了它..." %(name, q.get())) time.sleep(1) #生成多个线程 p = threading.Thread(target=Producer,args=("derek",)) c = threading.Thread(target=Consumer,args=("chihuo1",)) c1 = threading.Thread(target=Consumer,args=("chihou2",)) p.start() c.start() c1.start()
进程
Python中的多线程无法利用多核优势 , 所以如果我们想要充分地使用多核CPU的资源 , 那么就只能靠多进程了
multiprocessing模块中提供了Process , Queue , Pipe , Lock , RLock , Event , Condition等组件 , 与threading模块有很多相似之处
1、创建进程:
####### 创建进程 ####### from multiprocessing import Process import time def func(name): time.sleep(2) print('hello',name) if __name__ == '__main__': p= Process(target=func,args=('derek',)) p.start() # p.join() print('end...')
2、进程间通信
不同进程间内存是不共享的,要想实现两个进程间的数据交换。进程间通信有两种主要形式 , 队列和管道
(1)Queue 队列
from multiprocessing import Process, Queue #Queue是进程排列 def f(test): test.put('22') #通过创建的子进程往队列添加数据,实线父子进程交互 if __name__ == '__main__': q = Queue() #父进程 q.put("11") p = Process(target=f, args=(q,)) #子进程 p.start() p.join() print("取到:",q.get_nowait()) print("取到:",q.get_nowait()) #父进程在创建子进程的时候就把q克隆一份给子进程 #通过pickle序列化、反序列化,来达到两个进程之间的交互 结果: 取到: 11 取到: 22
(2)Pipe 管道
1 from multiprocessing import Process, Pipe 2 3 def f(conn): 4 conn.send('11') 5 conn.send('22') 6 print("from parent:",conn.recv()) 7 print("from parent:", conn.recv()) 8 conn.close() 9 10 if __name__ == '__main__': 11 parent_conn, child_conn = Pipe() #生成管道实例,可以互相send()和recv() 12 13 p = Process(target=f, args=(child_conn,)) 14 p.start() 15 16 print(parent_conn.recv()) # prints "11" 17 print(parent_conn.recv()) # prints "22" 18 parent_conn.send("33") # parent 发消息给 child 19 parent_conn.send("44") 20 p.join()
3、manager
进程之间是相互独立的 ,Queue和pipe只是实现了数据交互,并没实现数据共享,Manager可以实现进程间数据共享 。
Manager还支持进程中的很多操作 , 比如Condition , Lock , Namespace , Queue , RLock , Semaphore等
from multiprocessing import Process, Manager import os def f(d, l): d[os.getpid()] =os.getpid() l.append(os.getpid()) print(l) if __name__ == '__main__': with Manager() as manager: d = manager.dict() #{} #生成一个字典,可在多个进程间共享和传递 l = manager.list(range(5)) #生成一个列表,可在多个进程间共享和传递 p_list = [] for i in range(2): p = Process(target=f, args=(d, l)) p.start() p_list.append(p) for res in p_list: #等待结果 res.join() print(d) print(l)
4、lock
from multiprocessing import Process, Lock def f(l, i): #l.acquire() print('hello world', i) #l.release() if __name__ == '__main__': lock = Lock() for num in range(100): Process(target=f, args=(lock, num)).start() #要把lock传到函数的参数l #lock防止在屏幕上打印的时候会乱
5、线程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有以下几个主要方法:
- apply:从进程池里取一个进程并执行
- apply_async:apply的异步版本
- terminate:立刻关闭线程池
- join:主进程等待所有子进程执行完毕,必须在close或terminate之后
5.close:等待所有进程结束后,才关闭线程池
1 from multiprocessing import Process, Pool 2 import time 3 import os 4 5 6 def Foo(i): 7 time.sleep(2) 8 # os.getpid():获得当前进程的进程号,系统每开辟一个新进程就会为他分配一个进程号。 9 print("in process", os.getpid()) 10 return i + 100 11 12 13 def Bar(arg): 14 print('-->exec done:', arg, os.getpid()) 15 16 17 if __name__ == '__main__': # 多进程,必须加这一句(windows系统) 18 pool = Pool(processes=3) # 允许进程池同时放入3个进程 19 print("主进程", os.getpid()) 20 21 for i in range(10): 22 pool.apply_async(func=Foo, args=(i,), callback=Bar) # 一次执行三个进程。callback=回调,执行完Foo(),接着执行Bar() 23 # pool.apply(func=Foo, args=(i,)) # 串行,一个个执行,没回调函数 24 25 print('end') # 最后执行 26 pool.close() 27 # join:进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。必须先close(),再join() 28 # join注释,则不等子进程,主程序执行完则结束 29 pool.join()
协程
协程(Coroutine) : 是单线程下的并发 , 又称微线程 , 纤程 . 协程是一种用户态的轻量级线程 , 即协程有用户自己控制调度
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态
使用协程的优缺点
优点 :
- 协程的切换开销更小 , 属于程序级别的切换 , 更加轻量级
- 单线程内就可以实现并发的效果 , 最大限度利用CPU
缺点 :
- 协程的本质是单线程下 , 无法利用多核 , 可以是一个程序开启多个进程 , 每个进程内开启多个线程 , 每个线程内开启协程
- 协程指的是单个线程 , 因而一旦协程出现阻塞 将会阻塞整个线程
1.Greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
1 from greenlet import greenlet 2 3 def test1(): 4 print(12) 5 gr2.switch() #到这里切换到gr2,执行test2() 6 print(34) 7 gr2.switch() #切换到上次gr2运行的位置 8 9 def test2(): 10 print(56) 11 gr1.switch() #切换到上次gr1运行的位置 12 print(78) 13 14 gr1 = greenlet(test1) #启动一个协程gr1 15 gr2 = greenlet(test2) #启动一个协程gr2 16 17 gr1.switch() #开始运行gr1
2.Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。
注:由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:
######### IO阻塞自动切换 ######### import gevent def foo(): print('Running in foo') gevent.sleep(2) print('阻塞时间最长,最后运行') def bar(): print('running in bar') gevent.sleep(1) print('foo()还在阻塞,这里第二个运行') def func3(): print("running in func3 ") gevent.sleep(0) print("其它两个还在IO阻塞先运行") #创建协程实例 gevent.joinall([ gevent.spawn(foo), #生成, gevent.spawn(bar), gevent.spawn(func3), ]) #遇到IO自动切换 结果: Running in foo running in bar running in func3 其它两个还在IO阻塞先运行 foo()还在阻塞,这里第二个运行 阻塞时间最长,最后运行 Process finished with exit code 0