一、python的多线程python
多线程就是在同一时刻执行多个不一样的程序,然而python中的多线程并不能真正的实现并行,这是因为cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有一个线程被执行。linux
多线程的特色:网络
线程比进程更轻量级,建立一个线程要比建立一个进程快10-100倍。多线程
线程共享全局变量。并发
因为GIL的缘由,当一个线程遇到IO操做时,会切换到另外一个线程,因此线程适合IO密集型操做。app
在多核cpu系统中,最大限度的利用多核,能够开启多个线程,开销比进程小的多,可是这并不适合python。dom
多线程互斥锁:socket
由于线程共享全局变量,因此须要互斥锁去限制线程对全局变量的更改。async
假设,当一个线程在执行到获取全局变量的时候,这个后GIL切换到另外一个线程执行,这个时候新的线程为全局变量+1后切换回以前的线程,以前的线程中的全局变量仍是+1前的值,因此须要互斥锁。函数
为何有了GIL锁还要互斥锁呢?
GIL锁只是控制同一时刻下只有一个线程被执行,这并不能控制同一时刻只有一个线程去获取并更改全局变量,因此须要使用互斥锁。
多线程的实现:
# 导入threading模块 import threading # 定义全局变量 i=0 # 定义互斥锁 mutex = threading.Lock() def a(): # 申明全局变量i global i for j in range(2000000): # 获取互斥锁 mutex.acquire() i+=1 # 释放互斥锁 mutex.release() def b(): global i for j in range(2000000): mutex.acquire() i+=1 mutex.release() # 建立线程 t1 = threading.Thread(target=a) t2 = threading.Thread(target=b) # 开启线程 t1.start() t2.start() # 等待全部线程结束 t1.join() t2.join() print(i)
二、python中的多进程
python的多线程不能利用多核的优点,若是想要充分的利用多核cpu的资源,python中大部分状况须要使用多进程。
python多进程的特色:
进程间不共享全局变量,进程修改的数据仅限于该进程内。
进程建立和销毁的开销比较大。
相对于线程,进程更适合与计算密集型操做。
能充分利用多核的优点。
进程间通讯:
既然进程间中不公共享全局变量,那么多进程间怎么进行通讯呢?能够使用multiprocessing中的Queue模块,固然也能够使用socket、管道、共享内存等方式。
多进程的实现:
# 导入multiprocessin模块 import multiprocessing # 建立队列 queue = multiprocessing.Queue() # 定义全局变量 a = 0 # 定义函数 def work1(num): # 获取队列中的数据,若是没有数据,将堵塞 a = queue.get() # 将队列中的数据+2000000次num for i in range(2000000): a+=num # 将数据存放在队列中 queue.put(a) # 打印最终结果 print("work1",a) # 定义函数 def work2(): # 申明全局变量a global a # 将a+2000000次1 for i in range(2000000): a+=1 # 打印最总结果 print("work2",a) # 将a存放在队列中 queue.put(a) # 建立进程 p1 = multiprocessing.Process(target=work1, args=(2,)) p2 = multiprocessing.Process(target=work2) # 启动进程 p1.start() p2.start() # 等待进程结束 p1.join() p2.join() # 获取队列中的数据 a = queue.get() # 打印a print(a)
进程池的实现
进程池能减小重复建立和销毁进程的开销问题
# 导入须要的模块 import multiprocessing import time import random # 定义函数 def work(num): print("num=",num) time.sleep(random.randint(0,2)) # 建立进程池,设置进程的数量 pool = multiprocessing.Pool(3) for i in range(10): # 开启进程 pool.apply_async(work, args=(i,)) # 设置等待时间,等待全部进程结束 time.sleep(20)
三、python中的协程
在linux中线程就是轻量级的进程,而咱们一般也把协程称为轻量级的线程。
对比进程和协程:
进程是内核调度,而协程是在用户态调度,因此说进程的上下文在内核态保存恢复,而协程是在用户态保存恢复的,因此协程的开销比进程低。
进程会被抢占,而协程不会,也就是说协程若是不主动让出cpu,那么其余的协程就没有执行的机会。
进程所须要的内存比协程大得多
对比线程和协程:
线程的上下文切换成本相对于协程来讲比较高。
线程的切换由操做系统来控制,而协程的切换由咱们本身控制。
yield实现协程:
# 定义两个函数 def work1(): while True: print("work1") # 当程序运行到yield就会暂停,等待下次的next调用,而后继续执行 yield def work2(): while True: print("work2") yield w1 = work1() w2 = work2() while True: # 使用next函数启动 next(w1) next(w2)
greenlet实现协程:
greenlet安装:
sudo pip3 install greenlet
code:
# 导入greenlet模块 from greenlet import greenlet def work1(): for i in range(10): print("work1") # 打印事后跳转至协程g2继续执行 g2.switch() def work2(): for i in range(10): print("work2") # 打印后跳转至协程g1继续执行 g1.switch() # 建立协程g1 g1 = greenlet(work1) # 建立协程g2 g2 = greenlet(work2) # 跳转至协程g1 g1.switch()
gevent实现协程:
gevent是基于greenlet的并发网络库,每当有一个协程堵塞的时,程序将自动调度。
monkey-patching:
通常称为猴子补丁,这个补丁能直接修改标准库里面大部分的阻塞式系统调用。可是若是在复杂的生产环境中使用了这些标准库,可能就会由于打了补丁而出现奇怪的问题。
gevent安装:
sudo pip3 install gevent
code:
# 导入所须要的模块 import gevent import time from gevent import monkey # 猴子补丁,monkey.patch_all()方法将全部的标准库都替换掉 # 使用猴子补丁褒贬不一,可是官网上仍是建议使用patch_all(),并且在程序的第一行就执行 monkey.patch_all() def f(n): for i in range(n): print(i) # 设置延时 time.sleep(0.5) # 若是没有导入monkey模块的话,须要使用gevent.sleep() # gevent.sleep(0.5) # ----------------写法一-------------------- # 建立greenlet协程对象 # g1 = gevent.spawn(f,5) # g2 = gevent.spawn(f,5) # g3 = gevent.spawn(f,5) # 等待全部greenlet携程结束后退出 # g1.join() # g2.join() # g3.join() # ----------------写法二-------------------- gevent.joinall([gevent.spawn(f,5), gevent.spawn(f,5), gevent.spawn(f,5)])